package api

import (
	"context"
	"sync/atomic"

	"github.com/gin-gonic/gin"
	"go.uber.org/fx"

	"github.com/pingcap-inc/clinic/pkg/api/activities"
	"github.com/pingcap-inc/clinic/pkg/api/activity_file"
	"github.com/pingcap-inc/clinic/pkg/api/cloud"
	"github.com/pingcap-inc/clinic/pkg/api/datasource"
	"github.com/pingcap-inc/clinic/pkg/api/diagnosis"
	error2 "github.com/pingcap-inc/clinic/pkg/api/error"
	"github.com/pingcap-inc/clinic/pkg/api/healthcheck"
	"github.com/pingcap-inc/clinic/pkg/api/internal_key"
	"github.com/pingcap-inc/clinic/pkg/api/login"
	"github.com/pingcap-inc/clinic/pkg/api/oldreport"
	"github.com/pingcap-inc/clinic/pkg/api/org"
	"github.com/pingcap-inc/clinic/pkg/api/report"
	"github.com/pingcap-inc/clinic/pkg/api/slowlog_explorer"
	"github.com/pingcap-inc/clinic/pkg/api/upload"
	"github.com/pingcap-inc/clinic/pkg/api/user"
	"github.com/pingcap-inc/clinic/pkg/auth"
	"github.com/pingcap-inc/clinic/pkg/auth/session"
	"github.com/pingcap-inc/clinic/pkg/config"
	"github.com/pingcap-inc/clinic/pkg/es"
	proxy2 "github.com/pingcap-inc/clinic/pkg/proxy"
	"github.com/pingcap-inc/clinic/pkg/ratelimit"
	"github.com/pingcap-inc/clinic/pkg/rbac"
	"github.com/pingcap-inc/clinic/pkg/resource_limit"
	"github.com/pingcap-inc/clinic/pkg/task_manager"
	"github.com/pingcap-inc/clinic/pkg/upload_file/decrypt"
	"github.com/pingcap-inc/clinic/pkg/upload_file/manager"
)

type APIService struct {
	Status *ServiceStatus

	Params *APIServiceParams
	RG     *gin.RouterGroup
}

type APIServiceParams struct {
	Ctx                context.Context
	Gin                *gin.Engine
	Session            *session.SessionManager
	Cfg                *config.Config
	Rbac               rbac.Auth
	Auth               *auth.Auth
	EsClient           *es.ESClient
	QuotaLimiter       resource_limit.IQuotaLimiter
	RateLimiter        *ratelimit.RateLimiterMiddleware
	Decrypt            *decrypt.Decrypt
	Manager            *manager.FileStoreageManager
	TaskManagerCli     *task_manager.TaskManagerClient
	MetricProxyHandler *proxy2.TiDBClusterMetricHandler
	O11yProxyHandler   *proxy2.O11yProxyHandler
}

func NewAPIService(params *APIServiceParams) *APIService {
	svc := &APIService{
		Params: params,
		Status: NewServiceStatus(),
	}
	svc.Params.Gin.Use(gin.Recovery())
	svc.Params.Gin.Use(error2.ErrorHandlerFn)
	svc.RG = svc.Params.Gin.Group("/clinic/api/v1")
	svc.RG.Use(svc.Params.Auth.MWTryAuth)
	svc.RG.Use(CORSMiddleware())
	svc.RG.Use(params.RateLimiter.Middleware)
	return svc
}

func CORSMiddleware() gin.HandlerFunc {
	return func(c *gin.Context) {
		c.Writer.Header().Set("Access-Control-Allow-Origin", "*")

		if c.Request.Method == "OPTIONS" {
			c.AbortWithStatus(204)
			return
		}

		c.Next()
	}
}

func (s *APIService) Start() error {
	if s.Status.IsRunning() {
		return nil
	}

	opts := []fx.Option{
		fx.Supply(
			s.Params.Gin,
			s.Params.Session,
			s.RG,
			s.Params.Cfg,
			s.Params.Rbac,
			fx.Annotate(s.Params.QuotaLimiter, fx.As(new(resource_limit.IQuotaLimiter))),
			s.Params.EsClient,
			s.Params.Decrypt,
			s.Params.Manager,
			s.Params.TaskManagerCli,
			s.Params.MetricProxyHandler,
			s.Params.O11yProxyHandler,
			fx.Annotate(s.Params.Rbac, fx.As(new(rbac.Auth))),
		),
		// fx.Supply(s),
		login.Moudel,
		user.Module,
		diagnosis.Module,
		org.Module,
		datasource.Module,
		upload.Module,
		oldreport.Module,
		report.Module,
		cloud.Module,
		healthcheck.Module,
		internal_key.Module,
		slowlog_explorer.Module,
		fx.Invoke(s.Status.Register),
	}
	if s.Params.Cfg.LinkTiDBCloud {
		opts = append(opts,
			activities.Module,
			activity_file.Module,
		)
	}

	app := fx.New(opts...)

	if err := app.Start(s.Params.Ctx); err != nil {
		return err
	}

	return nil
}

type ServiceStatus int32

func NewServiceStatus() *ServiceStatus {
	return new(ServiceStatus)
}

func (s *ServiceStatus) IsRunning() bool {
	return atomic.LoadInt32((*int32)(s)) != 0
}

func (s *ServiceStatus) Start() {
	atomic.StoreInt32((*int32)(s), 1)
}

func (s *ServiceStatus) Stop() {
	atomic.StoreInt32((*int32)(s), 0)
}

func (s *ServiceStatus) Register(lc fx.Lifecycle) {
	lc.Append(fx.Hook{
		OnStart: func(ctx context.Context) error {
			select {
			case <-ctx.Done():
				return ctx.Err()
			default:
				s.Start()
				return nil
			}
		},
		OnStop: func(context.Context) error {
			s.Stop()
			return nil
		},
	})
}
