package dep

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

	"github.com/jmoiron/sqlx"
	echo "github.com/labstack/echo/v4"
	"github.com/labstack/echo/v4/middleware"
	"github.com/robfig/cron"

	echoLog "github.com/labstack/gommon/log"

	"github.com/tidbcloud/pov-service/internal/client/email"
	"github.com/tidbcloud/pov-service/internal/client/email/sendgrid"
	"github.com/tidbcloud/pov-service/internal/client/rms"
	"github.com/tidbcloud/pov-service/internal/client/tcms"
	"github.com/tidbcloud/pov-service/internal/client/tiworkload"
	"github.com/tidbcloud/pov-service/internal/config"
	"github.com/tidbcloud/pov-service/internal/db/mysql"
	"github.com/tidbcloud/pov-service/internal/gateway"
	"github.com/tidbcloud/pov-service/internal/handler"
	"github.com/tidbcloud/pov-service/internal/handler/mymiddleware"
	"github.com/tidbcloud/pov-service/internal/mock"
	"github.com/tidbcloud/pov-service/internal/pkg/log"

	echoSwagger "github.com/swaggo/echo-swagger"
	"github.com/tidbcloud/pov-service/internal/service"
	"github.com/tidbcloud/pov-service/internal/service/workload"
	"go.uber.org/dig"
)

type Deps struct {
	Container *dig.Container
}

func NewDeps() *Deps {
	return &Deps{
		Container: dig.New(),
	}
}

type depParam struct {
	*config.Config
	log.Logger
}

func (d *Deps) Init(
	conf *config.Config, logger log.Logger) error {
	param := depParam{
		Config: conf,
		Logger: logger,
	}

	if err := d.initBase(param); err != nil {
		return err
	}
	if err := d.initRepo(param); err != nil {
		return err
	}
	if err := d.initClient(param); err != nil {
		return err
	}
	if err := d.initService(param); err != nil {
		return err
	}
	if err := d.initHandler(param); err != nil {
		return err
	}
	return nil
}

func (d *Deps) initBase(param depParam) error {
	bases := []any{
		func() depParam { return param },
		func() *config.Config { return param.Config },
		func() log.Logger { return param.Logger },
		func(cfg *config.Config) *echo.Echo {
			e := echo.New()

			e.GET("/ping", func(c echo.Context) error { return c.String(http.StatusOK, "pong") })
			e.GET("/swagger/*", echoSwagger.WrapHandler)

			e.Pre(middleware.RemoveTrailingSlash())
			e.Use(middleware.BodyLimit("2M"))
			e.Use(middleware.CORSWithConfig(middleware.CORSConfig{
				AllowOriginFunc: func(origin string) (bool, error) {
					return strings.HasPrefix(origin, "http://localhost:") ||
						strings.HasPrefix(origin, "http://127.0.0.1:") ||
						strings.HasPrefix(origin, "http://10.2.12.57:") ||
						strings.Contains(origin, "tidbcloud.netlify.app"), nil
				},
				AllowMethods:     []string{http.MethodGet, http.MethodPost, http.MethodPut, http.MethodPatch, http.MethodDelete, http.MethodOptions},
				AllowHeaders:     []string{echo.HeaderOrigin, echo.HeaderContentType, echo.HeaderAccept, echo.HeaderAuthorization},
				AllowCredentials: true,
			}))
			e.Use(middleware.Secure())
			e.Use(middleware.RecoverWithConfig(middleware.RecoverConfig{
				LogLevel: echoLog.ERROR,
			}))

			e.HTTPErrorHandler = mymiddleware.HandleHttpError

			return e
		},
		func() *cron.Cron {
			return cron.New()
		},
	}

	for _, f := range bases {
		if err := d.Container.Provide(f); err != nil {
			return err
		}
	}
	return nil
}

func (d *Deps) initRepo(param depParam) error {
	// Initialize database connection with automatic migrations
	if err := d.Container.Provide(func(cfg *config.Config, logger log.Logger) (*sqlx.DB, error) {
		logger.Infof("Connecting to MySQL database: %s:%d/%s", cfg.Database.Host, cfg.Database.Port, cfg.Database.Database)

		database, err := mysql.NewDB(cfg.Database)
		if err != nil {
			return nil, err
		}

		// Run database migrations
		logger.Infof("Running database migrations...")
		migrationManager := mysql.NewMigrationManager(database, logger)
		if err := migrationManager.RunMigrations(); err != nil {
			database.Close()
			return nil, fmt.Errorf("failed to run database migrations: %w", err)
		}
		logger.Infof("Database migrations completed successfully")

		return database, nil
	}); err != nil {
		return err
	}

	repos := []any{
		mysql.NewWorkloadTest,
		mysql.NewAuditLogRepo,
		mysql.NewWorkloadSampleRepo,
		mysql.NewScriptRepo,
		mysql.NewScriptExecRepo,
	}

	for _, f := range repos {
		// Initialize repository
		if err := d.Container.Provide(f); err != nil {
			return err
		}
	}

	return nil
}

func (d *Deps) initClient(param depParam) error {
	bases := []any{
		func(c *config.Config) tiworkload.Client {
			return tiworkload.NewClient(c.TiWorkloadAddress, c.TiWorkloadAk, c.TiWorkloadSk)
		},
		func(c *config.Config) tcms.Client {
			return tcms.NewClient(c.TcmsAddress, c.TcmsAuth, c.TcmsToken, c.TcmsWorkflowRunnerID)
		},
		func(c *config.Config) rms.Client {
			return rms.NewClient(c.RmsAddress, c.TcmsToken)
		},
		func(cfg *config.Config) email.Client {
			if cli, err := sendgrid.NewClient(cfg.SendGridApiKey); err != nil {
				panic(err)
			} else {
				return cli
			}
		},
		gateway.NewWorkload,
	}

	for _, f := range bases {
		if err := d.Container.Provide(f); err != nil {
			return err
		}
	}

	return nil
}

func (d *Deps) initService(depParam) error {
	bases := []any{
		// Provide the workload service, wrapped with a mock adaptor if mocking is enabled.
		func(param workload.WorkloadServiceParams) service.Workload {
			svc := workload.NewWorkloadService(param)
			return mock.NewAdaptor(svc, param.TestRepo)
		},
		workload.NewRunner,
		workload.NewScriptService,
		workload.NewScriptRunner,
	}

	for _, f := range bases {
		if err := d.Container.Provide(f); err != nil {
			return err
		}
	}

	if err := d.Container.Provide(service.NewManager); err != nil {
		return err
	}
	return nil
}

func (d *Deps) initHandler(depParam) error {
	handlers := []any{
		handler.NewWorkload,
	}

	for _, f := range handlers {
		if err := d.Container.Provide(f); err != nil {
			return err
		}
	}
	if err := d.Container.Provide(handler.NewManager); err != nil {
		return err
	}
	return nil
}
