package main

import (
	"context"
	"fmt"
	"net/http"
	"strings"
	"time"

	authApi "git.vdhsn.com/barretthousen/barretthousen/src/auth/api"
	aapi "git.vdhsn.com/barretthousen/barretthousen/src/auth/api/grpc"
	"git.vdhsn.com/barretthousen/barretthousen/src/lib/kernel"
	api "git.vdhsn.com/barretthousen/barretthousen/src/runner/api/grpc"
	"github.com/grpc-ecosystem/grpc-gateway/v2/runtime"
	"go.uber.org/dig"
	"google.golang.org/grpc"
	"google.golang.org/grpc/backoff"
	"google.golang.org/grpc/credentials/insecure"
)

type ProxyAdminApp struct {
	LogLevel  kernel.LogLevel `yaml:"log_level" yaml-default:"0"`
	Port      int             `yaml:"port" `
	Endpoints struct {
		Runner string `yaml:"runner" `
		Auth   string `yaml:"auth"`
	} `yaml:"endpoints"`
}

type AuthService interface {
	CheckSession(context.Context, authApi.CheckSessionParams) (authApi.Account, error)
}

func (app *ProxyAdminApp) Start(ctx context.Context) error {
	ioc := dig.New()

	var err error
	if err = ioc.Provide(func() *runtime.ServeMux {
		return runtime.NewServeMux()
	}); err != nil {
		return err
	}

	if err = ioc.Provide(func() (grpc.ClientConnInterface, error) {
		return kernel.DialGRPC(app.Endpoints.Auth)
	}); err != nil {
		return err
	}

	if err = ioc.Provide(func(conn grpc.ClientConnInterface) AuthService {
		return authApi.NewAuthServiceClient(conn)
	}); err != nil {
		return err
	}

	if err = ioc.Invoke(func(grpcMux *runtime.ServeMux, authClient AuthService) error {
		// TODO: refactor into kernel package
		if err := api.RegisterRunnerHandlerFromEndpoint(ctx, grpcMux, app.Endpoints.Runner, []grpc.DialOption{
			grpc.WithTransportCredentials(insecure.NewCredentials()),
			grpc.WithConnectParams(grpc.ConnectParams{
				Backoff: backoff.Config{
					MaxDelay: time.Second * 3,
				},
				MinConnectTimeout: time.Second,
			}),
		}); err != nil {
			return err
		}

		// TODO: refactor into kernel package
		if err := aapi.RegisterAuthHandlerFromEndpoint(ctx, grpcMux, app.Endpoints.Auth, []grpc.DialOption{
			grpc.WithTransportCredentials(insecure.NewCredentials()),
			grpc.WithConnectParams(grpc.ConnectParams{
				Backoff: backoff.Config{
					MaxDelay: time.Second * 3,
				},
				MinConnectTimeout: time.Second,
			}),
		}); err != nil {
			return err
		}

		kernel.TraceLog.Printf("%+v", app)
		httpServer := &http.Server{
			Addr:              fmt.Sprintf("0.0.0.0:%d", app.Port),
			ReadHeaderTimeout: time.Second,
			Handler: http.StripPrefix("/api", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
				kernel.TraceLog.Printf("{ \"Client\": \"%s\", \"Path\":\"%s\", \"User-Agent\":\"%s\", \"Host\":\"%s\", \"Origin\":\"%s\"} ", r.RemoteAddr, r.URL, r.UserAgent(), r.Host, r.Header.Get("Origin"))

				// TODO: move to a middleware package
				if strings.HasPrefix(r.Host, "proxy-") {
					w.Header().Set("Access-Control-Allow-Origin", r.Header.Get("Origin"))
				}

				// TODO: move to a middleware package
				if !(strings.HasSuffix(r.URL.Path, "user") && r.Method == http.MethodPost) {
					sessIdStr := r.Header.Get("bh-session-id")
					kernel.TraceLog.Printf("session %s", sessIdStr)
					account, err := authClient.CheckSession(r.Context(), authApi.CheckSessionParams{
						SessionID: sessIdStr,
					})
					if err != nil {
						kernel.ErrorLog.Printf("error calling auth service: %v", err)
						http.Error(w, "must be logged in as admin", http.StatusForbidden)
						return
					}

					kernel.TraceLog.Printf("{ \"session-id\":\"%s\", \"email\":\"%s\", \"accountId\":\"%d\", \"role\":\"%s\"}", sessIdStr, account.Email, account.ID, account.Role)
					if account.Role != "ADMINISTRATOR" {
						http.Error(w, "must be administrator", http.StatusUnauthorized)
						return
					}

				}

				grpcMux.ServeHTTP(w, r)
			})),
		}

		kernel.InfoLog.Printf("Starting HTTP proxy @ %q", httpServer.Addr)

		return httpServer.ListenAndServe()
	}); err != nil {
		return err
	}

	return nil
}

func (app *ProxyAdminApp) OnStop(ctx context.Context) {
}

func (app *ProxyAdminApp) GetLogLevel() kernel.LogLevel { return app.LogLevel }

func main() {
	kernel.Run(context.Background(), &ProxyAdminApp{
		LogLevel: kernel.LevelTrace,
		Port:     80,
	})
}
