package server

import (
	"context"
	"flag"
	"fmt"
	"net/http"
	"net/url"
	"os"

	"github.com/gin-contrib/requestid"
	"github.com/gin-gonic/gin"
	"github.com/go-logr/logr"
	"github.com/peterbourgon/ff/v3"

	"edge-infra.dev/pkg/edge/api/middleware"
	"edge-infra.dev/pkg/lib/fog"
	"edge-infra.dev/pkg/sds/emergencyaccess/client"
	"edge-infra.dev/pkg/sds/emergencyaccess/config"
	"edge-infra.dev/pkg/sds/emergencyaccess/eaconst"
	"edge-infra.dev/pkg/sds/emergencyaccess/eagateway"
	eamiddleware "edge-infra.dev/pkg/sds/emergencyaccess/middleware"
	"edge-infra.dev/pkg/sds/emergencyaccess/msgsvc"
	"edge-infra.dev/pkg/sds/emergencyaccess/remotecli"
	"edge-infra.dev/pkg/sds/emergencyaccess/requestservice"
)

const (
	defaultAuthorizeCommandPath = "authorizeCommand"
	defaultAuthorizeRequestPath = "authorizeRequest"
	defaultAuthorizeTargetPath  = "authorizeTarget"
	defaultAuthorizeUserPath    = "authorizeUser"
	defaultResolveTargetPath    = "resolveTarget"
)

var (
	ErrUnauthorizedUser = fmt.Errorf("user is unauthorized")
)

type GatewayServer struct {
	GinEngine      *gin.Engine
	rcli           eagateway.RemoteCLI
	log            logr.Logger
	client         *http.Client
	requestService *requestservice.RequestService

	authorizeCommandURL *url.URL
	authorizeRequestURL *url.URL
	resolveTargetURL    *url.URL
	authorizeTargetURL  *url.URL
	authorizeUserURL    *url.URL
}

// New returns an EAGAteway service with our standard pattern. Function will
// return an error if setAuthServiceURL fails.
func New(config eagateway.Config, router *gin.Engine, log logr.Logger, rcli eagateway.RemoteCLI, requestService *requestservice.RequestService, checks ...func() error) (server *GatewayServer, err error) {
	cl := client.New()
	server = &GatewayServer{
		GinEngine:      router,
		rcli:           rcli,
		requestService: requestService,
		log:            log,
		client:         cl,
	}
	err = server.setAuthServiceURLs(config)
	if err != nil {
		return nil, err
	}
	server.newGinServer(checks...)
	return server, nil
}

func (server *GatewayServer) setAuthServiceURLs(config eagateway.Config) error {
	host, err := url.Parse("http://" + config.AuthServiceHost)
	if err != nil {
		return err
	}
	server.authorizeCommandURL = host.JoinPath(defaultAuthorizeCommandPath)
	server.authorizeRequestURL = host.JoinPath(defaultAuthorizeRequestPath)
	server.resolveTargetURL = host.JoinPath(defaultResolveTargetPath)
	server.authorizeTargetURL = host.JoinPath(defaultAuthorizeTargetPath)
	server.authorizeUserURL = host.JoinPath(defaultAuthorizeUserPath)
	return nil
}

func (server *GatewayServer) newGinServer(checks ...func() error) {
	router := server.GinEngine
	router.ContextWithFallback = true

	router.Use(middleware.SetRequestContext())
	router.Use(gin.Recovery())

	// public endpoints
	router.Any("/health", eamiddleware.HealthCheck(checks...))

	router.Any("/ready", func(c *gin.Context) {
		c.String(http.StatusOK, "ok")
	})

	public := router.Group("/ea")
	public.Use(requestid.New(requestid.WithCustomHeaderStrKey(eamiddleware.CorrelationIDKey)))
	public.Use(eamiddleware.VerifyAPIVersion(eaconst.APIVersion))
	public.Use(eamiddleware.SetLoggerInContext(server.log))
	public.Use(eamiddleware.RequestBookendLogs())
	public.Use(eamiddleware.SaveAuthToContext())

	// authorized endpoints
	authorized := public.Group("/")

	authorized.POST("/startSession", server.StartSession)
	authorized.POST("/sendCommand", server.SendCommand)
	authorized.POST("/endSession", server.EndSession)
}

func Run() error {
	conf := Config{}
	flags := flag.NewFlagSet("eagateway", flag.ExitOnError)
	conf.BindFlags(flags)
	// flags passed as --auth-service-host from cli or AUTH_SERVICE_HOST in env will be parsed here.
	if err := ff.Parse(flags, os.Args[1:], ff.WithEnvVarNoPrefix(), ff.WithIgnoreUndefined(true)); err != nil {
		return err
	}

	router := gin.New()
	log := newLogger()

	// Remotecli uses the global logger from context for some log messages
	ctx := fog.IntoContext(context.Background(), log)

	ms, err := msgsvc.NewMessageService(ctx)
	if err != nil {
		return fmt.Errorf("failed to initialise message service: %w", err)
	}

	rcli := remotecli.New(ctx, ms)

	db, check, err := config.DB(conf.SQL)
	if err != nil {
		return fmt.Errorf("error connecting to database: %w", err)
	}
	requestService, err := requestservice.New(db)
	if err != nil {
		return fmt.Errorf("failed to initialise request service: %w", err)
	}

	server, err := New(conf.EAGAteway, router, log, rcli, requestService, check)
	if err != nil {
		return err
	}
	return server.GinEngine.Run()
}