package apiimp

import (
	"os"

	"github.com/go-chi/chi/v5"
	cm "github.com/go-chi/chi/v5/middleware"
	"github.com/go-chi/cors"
	"github.com/go-chi/metrics"
	api "github.com/tech/sendico/pkg/api/http"
	amr "github.com/tech/sendico/pkg/api/routers"
	"github.com/tech/sendico/pkg/api/routers/health"
	"github.com/tech/sendico/pkg/auth"
	"github.com/tech/sendico/pkg/db"
	"github.com/tech/sendico/pkg/messaging"
	notifications "github.com/tech/sendico/pkg/messaging/notifications/processor"
	"github.com/tech/sendico/pkg/mlogger"
	"github.com/tech/sendico/pkg/mservice"
	"github.com/tech/sendico/server/interface/api/sresponse"
	wsh "github.com/tech/sendico/server/interface/api/ws"
	"github.com/tech/sendico/server/interface/middleware"
	"github.com/tech/sendico/server/internal/api/routers"
	mr "github.com/tech/sendico/server/internal/api/routers/metrics"
	"github.com/tech/sendico/server/internal/api/ws"
	"go.uber.org/zap"
	"moul.io/chizap"
)

type Middleware struct {
	logger       mlogger.Logger
	router       *chi.Mux
	apiEndpoint  string
	health       amr.Health
	metrics      mr.Metrics
	wshandler    ws.Router
	messaging    amr.Messaging
	epdispatcher *routers.Dispatcher
}

func (mw *Middleware) Handler(service mservice.Type, endpoint string, method api.HTTPMethod, handler sresponse.HandlerFunc) {
	mw.epdispatcher.Handler(service, endpoint, method, handler)
}

func (mw *Middleware) AccountHandler(service mservice.Type, endpoint string, method api.HTTPMethod, handler sresponse.AccountHandlerFunc) {
	mw.epdispatcher.AccountHandler(service, endpoint, method, handler)
}

func (mw *Middleware) PendingAccountHandler(service mservice.Type, endpoint string, method api.HTTPMethod, handler sresponse.PendingAccountHandlerFunc) {
	mw.epdispatcher.PendingAccountHandler(service, endpoint, method, handler)
}

func (mw *Middleware) WSHandler(messageType string, handler wsh.HandlerFunc) {
	mw.wshandler.InstallHandler(messageType, handler)
}

func (mw *Middleware) Consumer(processor notifications.EnvelopeProcessor) error {
	return mw.messaging.Consumer(processor)
}

func (mw *Middleware) Producer() messaging.Producer {
	return mw.messaging.Producer()
}

func (mw *Middleware) Messaging() messaging.Register {
	return mw
}

func (mw *Middleware) Finish() {
	mw.messaging.Finish()
	mw.health.Finish()
}

func (mw *Middleware) SetStatus(status health.ServiceStatus) {
	mw.health.SetStatus(status)
}

func (mw *Middleware) installMiddleware(config *middleware.Config, debug bool) {
	mw.logger.Debug("Installing middleware stack...")
	// Collect metrics for all incoming HTTP requests
	mw.router.Use(metrics.Collector(metrics.CollectorOpts{
		Host:  false, // avoid high-cardinality "host" label
		Proto: true,  // include HTTP protocol label
	}))
	mw.router.Use(cm.RequestID)
	mw.router.Use(cm.RealIP)
	if debug {
		mw.router.Use(chizap.New(mw.logger.Named("http_trace"), &chizap.Opts{
			WithReferer:   true,
			WithUserAgent: true,
		}))
	}
	mw.router.Use(cors.Handler(cors.Options{
		AllowedOrigins:     config.CORS.AllowedOrigins,
		AllowedMethods:     config.CORS.AllowedMethods,
		AllowedHeaders:     config.CORS.AllowedHeaders,
		ExposedHeaders:     config.CORS.ExposedHeaders,
		AllowCredentials:   config.CORS.AllowCredentials,
		MaxAge:             config.CORS.MaxAge,
		OptionsPassthrough: false,
		Debug:              debug,
	}))
	mw.router.Use(cm.Recoverer)
	mw.router.Handle("/metrics", metrics.Handler())
	mw.logger.Info("Middleware stack installation complete")
}

func CreateMiddleware(logger mlogger.Logger, db db.Factory, enforcer auth.Enforcer, router *chi.Mux, config *middleware.Config, debug bool) (*Middleware, error) {
	p := &Middleware{
		logger:      logger.Named("middleware"),
		router:      router,
		apiEndpoint: os.Getenv(config.EndPointEnv),
	}
	p.logger.Info("Set endpoint", zap.String("endpoint", p.apiEndpoint))
	p.installMiddleware(config, debug)
	var err error
	if p.messaging, err = amr.NewMessagingRouter(p.logger, &config.Messaging); err != nil {
		p.logger.Error("Failed to create messaging router", zap.Error(err))
		return nil, err
	}
	if p.health, err = amr.NewHealthRouter(p.logger, p.router, p.apiEndpoint); err != nil {
		p.logger.Error("Failed to create healthcheck router", zap.Error(err), zap.String("api_endpoint", p.apiEndpoint))
		return nil, err
	}
	if p.metrics, err = mr.NewMetricsRouter(p.logger, p.router, p.apiEndpoint); err != nil {
		p.logger.Error("Failed to create metrics router", zap.Error(err), zap.String("api_endpoint", p.apiEndpoint))
		return nil, err
	}

	adb, err := db.NewAccountDB()
	if err != nil {
		p.logger.Error("Faild to create account database", zap.Error(err))
		return nil, err
	}
	rtdb, err := db.NewRefreshTokensDB()
	if err != nil {
		p.logger.Error("Faild to create refresh token management database", zap.Error(err))
		return nil, err
	}

	cdb, err := db.NewVerificationsDB()
	if err != nil {
		p.logger.Error("Failed to create confirmations database", zap.Error(err))
		return nil, err
	}

	p.epdispatcher = routers.NewDispatcher(p.logger, p.router, adb, cdb, rtdb, enforcer, config)
	p.wshandler = ws.NewRouter(p.logger, p.router, &config.WebSocket, p.apiEndpoint)
	return p, nil
}
