package handler

import (
	"context"
	"net/http"
	"time"

	healthdomain "github.com/AlchemyTelcoSolutions/health-go/domain"
	httpproducer "github.com/AlchemyTelcoSolutions/health-go/producer/http"
	grpcresolver "github.com/AlchemyTelcoSolutions/health-go/resolver/grpc"
	httprestresolver "github.com/AlchemyTelcoSolutions/health-go/resolver/http_rest"
	"github.com/AlchemyTelcoSolutions/utils/pointer"
	commonDomain "github.com/AlchemyTelcoSolutions/utils/service/domain"
	"github.com/AlchemyTelcoSolutions/xutils-go/xlogger"
	"github.com/go-chi/chi/v5"
	"github.com/go-chi/chi/v5/middleware"
	"github.com/go-chi/cors"
	"github.com/go-resty/resty/v2"
	"github.com/prometheus/client_golang/prometheus/promhttp"

	v1 "github.com/AlchemyTelcoSolutions/callisto-so-bff/api/v1"
	"github.com/AlchemyTelcoSolutions/callisto-so-bff/internal/api"
	apimiddlewares "github.com/AlchemyTelcoSolutions/callisto-so-bff/internal/api/middlewares"
)

const (
	apiVersion = "/v1"
)

func NewHandler(ctx context.Context, config Config) (http.Handler, error) {
	if config.Logger == nil {
		config.Logger = xlogger.NoOpLogger{}
	}
	r := chi.NewRouter() // http.Handler

	// public auth router group
	r.Group(func(r chi.Router) {
		r.Mount("/metrics", promhttp.Handler())
	})

	mountHealthRoutes(ctx, config, r)

	r.Group(func(r chi.Router) {
		// cors
		configs := config.Configurations.GetConfigurations()
		r.Use(cors.Handler(cors.Options{
			// AllowedOrigins: []string{"https://foo.com"}, // Use this to allow specific origin hosts
			AllowedOrigins: configs.HTTP.AllowedOrigins,
			// AllowOriginFunc:  func(r *http.Request, origin string) bool { return true },
			AllowCredentials: true,
			AllowedMethods:   []string{"GET", "POST", "PUT", "DELETE", "OPTIONS", "PATCH"},
			AllowedHeaders: []string{
				"Accept", "Authorization", "Content-Type", "X-CSRF-Token",
				"Access-Control-Allow-Headers", "X-Requested-With",
				"Access-Control-Request-Method", "Access-Control-Request-Headers",
			},
			MaxAge: 300, // Maximum value not ignored by any of major browsers
		}))

		r.Use(jsonContentType)
		r.Use(middleware.Recoverer)
		r.Use(setLogger(config.Logger))
		r.Use(SetRequestLogger(config.Logger))
		r.Use(SetResponseLogger(config.Logger))

		// For all Not Found paths in the /api/v1 group redirect to callisto-api
		// if config is enabled
		if configs.HTTP.Proxy.IsEnabled {
			r.Use(apimiddlewares.AllowedPaths(config.Logger, configs.HTTP.Proxy))
			r.Use(apimiddlewares.ProxyNotFoundPaths(config.Logger, config.Server))
		}

		// Convert legacy order id to order ref
		if configs.CallistoSO.Enabled {
			r.Use(apimiddlewares.GetOrderRefByLegacyID(config.CallistoSOGrpcClient))
		}

		r.Mount(configs.HTTP.ApiPrefix, v1.HandlerWithOptions(config.Server, v1.ChiServerOptions{
			BaseRouter:       r,
			BaseURL:          configs.HTTP.ApiPrefix + apiVersion,
			ErrorHandlerFunc: handleErrors,
		}))

	})

	return r, nil
}

func handleErrors(w http.ResponseWriter, r *http.Request, err error) {
	api.RespWithErr(r, w, commonDomain.NewBadRequestError(err.Error()))
}

func jsonContentType(next http.Handler) http.Handler {
	fn := func(w http.ResponseWriter, r *http.Request) {
		w.Header().Set("Content-Type", "application/json")
		next.ServeHTTP(w, r)
	}
	return http.HandlerFunc(fn)
}

// setLogger adds a tracing logger to each request
func setLogger(logger xlogger.Logger) func(next http.Handler) http.Handler {
	return func(next http.Handler) http.Handler {
		fn := func(w http.ResponseWriter, r *http.Request) {
			ctx := setContext(r, logger)
			next.ServeHTTP(w, r.WithContext(ctx))
		}
		return http.HandlerFunc(fn)
	}
}

func mountHealthRoutes(ctx context.Context, c Config, router *chi.Mux) {
	const (
		healthCheckTimeout = 10 * time.Second
		callistoSOGRPC     = "callisto_so_grpc"
		callistoAPI        = "callisto_api"
	)

	services := make(map[string]healthdomain.HealthPingResolver)

	if c.CallistoSOGrpcClient != nil {
		services[callistoSOGRPC] = grpcresolver.NewResolver(grpcresolver.Options{
			ClientConn:      c.CallistoSOGrpcClient,
			FailureLevel:    pointer.ToPointer(healthdomain.Failure),
			CheckForStartup: true,
			CheckForReady:   true,
		})
	}

	cfg := c.Configurations.GetConfigurations()

	httpClient := resty.New().SetHeader("Content-Type", "application/json").SetTimeout(healthCheckTimeout)
	services[callistoAPI] = httprestresolver.NewResolver(httprestresolver.Options{
		Client:          httpClient,
		Url:             cfg.CallistoAPI.Host + "/health",
		FailureLevel:    pointer.ToPointer(healthdomain.Failure),
		CheckForStartup: true,
		CheckForReady:   true,
	})

	mountPathFunc := func(path string, handlerFunc http.HandlerFunc) {
		router.HandleFunc(path, handlerFunc)
	}

	routesMounter := httpproducer.NewRoutesMounter(ctx, mountPathFunc, httpproducer.Options{
		ServicesToCheck: services,
		Description:     "callisto-so-bff: Callisto sale Order backend for front end service",
		ThresholdPing:   healthCheckTimeout,
		Logger:          c.Logger.ZapLogger(),
	})

	routesMounter.MountHealthRoutes()
}
