package initialize

import (
	"database/sql"
	"embed"
	"fmt"
	"html/template"
	"log"
	"net/http"
	"os"

	"github.com/perfect-panel/server/internal/report"
	"github.com/perfect-panel/server/pkg/logger"
	"gorm.io/driver/mysql"

	"github.com/gin-gonic/gin"
	"github.com/google/uuid"
	"github.com/perfect-panel/server/initialize/migrate"
	"github.com/perfect-panel/server/internal/config"
	"github.com/perfect-panel/server/pkg/conf"
	"github.com/perfect-panel/server/pkg/orm"
	"github.com/perfect-panel/server/pkg/tool"
	"github.com/pkg/errors"
	"gopkg.in/yaml.v3"
	"gorm.io/gorm"
)

//go:embed templates/*.html
var templateFS embed.FS

var initStatus = make(chan bool)
var configPath string

func Config(path string) (chan bool, *http.Server) {
	// Set the configuration file path
	configPath = path
	// Create a new Gin instance
	r := gin.Default()
	// get server port
	port := 8080
	host := "127.0.0.1"

	// check gateway mode
	if report.IsGatewayMode() {
		// get free port
		freePort, err := report.ModulePort()
		if err != nil {
			logger.Errorf("get module port error: %s", err.Error())
			panic(err)
		}
		port = freePort
		// register module
		err = report.RegisterModule(port)
		if err != nil {
			logger.Errorf("register module error: %s", err.Error())
			panic(err)
		}
		logger.Infof("module registered on port %d", port)
	}
	// Create a new HTTP server
	server := &http.Server{
		Addr:    fmt.Sprintf("%s:%d", host, port),
		Handler: r,
	}
	// Load templates
	tmpl := template.Must(template.ParseFS(templateFS, "templates/*.html"))
	r.SetHTMLTemplate(tmpl)

	r.GET("/init", handleInit)
	r.POST("/init/config", handleInitConfig)
	r.POST("/init/mysql/test", HandleMySQLTest)
	r.POST("/init/redis/test", HandleRedisTest)
	// Handle 404
	r.NoRoute(func(c *gin.Context) {
		c.Redirect(http.StatusFound, "/init")
	})

	go func(server *http.Server) {
		// Start the server
		if err := server.ListenAndServe(); err != nil && !errors.Is(err, http.ErrServerClosed) {
			log.Fatalf("listen: %s\n", err)
		}
	}(server)

	return initStatus, server
}

func handleInit(c *gin.Context) {
	c.HTML(http.StatusOK, "index.html", nil)
}
func handleInitConfig(c *gin.Context) {
	// Load configuration file

	var cfg config.File
	conf.MustLoad(configPath, &cfg)
	var request struct {
		AdminEmail    string `json:"adminEmail"`
		AdminPassword string `json:"adminPassword"`

		MysqlHost     string `json:"mysqlHost"`
		MysqlPort     string `json:"mysqlPort"`
		MysqlDatabase string `json:"mysqlDatabase"`
		MysqlUser     string `json:"mysqlUser"`
		MysqlPassword string `json:"mysqlPassword"`

		RedisHost     string `json:"redisHost"`
		RedisPort     string `json:"redisPort"`
		RedisPassword string `json:"redisPassword"`
	}
	if err := c.ShouldBindJSON(&request); err != nil {
		c.JSON(http.StatusBadRequest, gin.H{
			"code": 400,
			"msg":  "Invalid request",
			"data": nil,
		})
		c.Abort()
		return
	}
	cfg.Debug = false
	// jwt secret
	cfg.JwtAuth.AccessSecret = uuid.New().String()
	// mysql
	cfg.MySQL.Addr = fmt.Sprintf("%s:%s", request.MysqlHost, request.MysqlPort)
	cfg.MySQL.Dbname = request.MysqlDatabase
	cfg.MySQL.Username = request.MysqlUser
	cfg.MySQL.Password = request.MysqlPassword
	// redis
	cfg.Redis.Host = fmt.Sprintf("%s:%s", request.RedisHost, request.RedisPort)
	cfg.Redis.Pass = request.RedisPassword

	// save config
	fileData, err := yaml.Marshal(cfg)
	if err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{
			"code": 500,
			"msg":  "Configuration initialization failed",
			"data": nil,
		})
		c.Abort()
		return
	}

	// create mysql connection
	db, err := orm.ConnectMysql(orm.Mysql{
		Config: orm.Config{
			Addr:          fmt.Sprintf("%s:%s", request.MysqlHost, request.MysqlPort),
			Username:      request.MysqlUser,
			Password:      request.MysqlPassword,
			Dbname:        request.MysqlDatabase,
			Config:        "charset%3Dutf8mb4%26parseTime%3Dtrue%26loc%3DLocal",
			MaxIdleConns:  10,
			MaxOpenConns:  10,
			SlowThreshold: 1000,
		},
	})
	if err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{
			"code": 500,
			"msg":  "MySQL connection failed",
			"data": nil,
		})
		c.Abort()
		return
	}
	dsn := fmt.Sprintf("%s:%s@tcp(%s:%s)/%s", request.MysqlUser, request.MysqlPassword, request.MysqlHost, request.MysqlPort, request.MysqlDatabase)
	// migrate database
	if err = migrate.Migrate(dsn).Up(); err != nil {
		logger.Errorf("[Init Mysql] Migrate failed: %v", err.Error())
		c.JSON(http.StatusOK, gin.H{
			"code": 500,
			"msg":  "Database migration failed",
			"data": nil,
		})
		c.Abort()
		return
	}

	// create admin user
	if err = migrate.CreateAdminUser(request.AdminEmail, request.AdminPassword, db); err != nil {
		logger.Errorf("[Init Mysql] Create admin user failed: %v", err.Error())
		c.JSON(http.StatusOK, gin.H{
			"code": 500,
			"msg":  "Admin user creation failed",
			"data": nil,
		})
		c.Abort()
		return
	}

	// write to file
	if err = os.WriteFile(configPath, fileData, 0644); err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{
			"code": 500,
			"msg":  "Configuration initialization failed",
			"data": nil,
		})
		c.Abort()
		return
	}

	c.JSON(http.StatusOK, gin.H{
		"code":   200,
		"msg":    "Configuration initialized",
		"status": true,
	})
	initStatus <- true
}

func HandleMySQLTest(c *gin.Context) {
	var request struct {
		Host     string `json:"host"`
		Port     string `json:"port"`
		Database string `json:"database"`
		User     string `json:"user"`
		Password string `json:"password"`
	}
	if err := c.ShouldBindJSON(&request); err != nil {
		c.JSON(http.StatusBadRequest, gin.H{
			"code": 400,
			"msg":  "Invalid request",
			"data": nil,
		})
		c.Abort()
		return
	}
	dsn := fmt.Sprintf("%s:%s@tcp(%s:%s)/%s", request.User, request.Password, request.Host, request.Port, request.Database)
	var status = true
	var message string
	var tx *sql.DB
	var tables []string
	db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
	if err != nil {
		logger.Errorf("connect mysql failed, err: %v\n", err.Error())
		status = false
		message = "MySQL connection failed"
		goto result
	}
	tx, _ = db.DB()
	if err := tx.Ping(); err != nil {
		logger.Errorf("ping mysql failed, err: %v\n", err.Error())
		status = false
		message = "MySQL connection failed"
	}

	tables, err = db.Migrator().GetTables()
	if err != nil {
		logger.Errorf("database table check failed, err: %v\n", err.Error())
		status = false
		message = "Database table check failed"
		goto result
	}
	if len(tables) > 0 {
		status = false
		message = "The database contains existing data. Please clear it before proceeding with the installation."
		goto result
	}

result:
	c.JSON(http.StatusOK, gin.H{
		"code":   200,
		"msg":    message,
		"status": status,
	})
}

func HandleRedisTest(c *gin.Context) {
	var request struct {
		Host     string `json:"host"`
		Port     string `json:"port"`
		Password string `json:"password"`
	}
	if err := c.ShouldBindJSON(&request); err != nil {
		c.JSON(http.StatusBadRequest, gin.H{
			"code": 400,
			"msg":  "Invalid request",
			"data": nil,
		})
		c.Abort()
		return
	}
	if err := tool.RedisPing(fmt.Sprintf("%s:%s", request.Host, request.Port), request.Password, 0); err != nil {
		c.JSON(http.StatusOK, gin.H{
			"code":   200,
			"msg":    nil,
			"status": false,
		})
		return
	}
	c.JSON(http.StatusOK, gin.H{
		"code":   200,
		"msg":    nil,
		"status": true,
	})
}
