package handlers

import (
	"fmt"
	"html/template"
	"log/slog"
	"net"
	"net/http"
	"os"
	"path/filepath"
	"strconv"
	"time"

	qfassets "git.mchus.pro/mchus/quoteforge"
	"git.mchus.pro/mchus/quoteforge/internal/db"
	"git.mchus.pro/mchus/quoteforge/internal/localdb"
	mysqlDriver "github.com/go-sql-driver/mysql"
	"github.com/gin-gonic/gin"
	gormmysql "gorm.io/driver/mysql"
	"gorm.io/gorm"
	"gorm.io/gorm/logger"
)

type SetupHandler struct {
	localDB    *localdb.LocalDB
	connMgr    *db.ConnectionManager
	templates  map[string]*template.Template
	restartSig chan struct{}
}

func NewSetupHandler(localDB *localdb.LocalDB, connMgr *db.ConnectionManager, templatesPath string, restartSig chan struct{}) (*SetupHandler, error) {
	funcMap := template.FuncMap{
		"sub": func(a, b int) int { return a - b },
		"add": func(a, b int) int { return a + b },
	}

	templates := make(map[string]*template.Template)

	// Load setup template (standalone, no base needed)
	setupPath := filepath.Join(templatesPath, "setup.html")
	var tmpl *template.Template
	var err error
	if stat, statErr := os.Stat(templatesPath); statErr == nil && stat.IsDir() {
		tmpl, err = template.New("").Funcs(funcMap).ParseFiles(setupPath)
	} else {
		tmpl, err = template.New("").Funcs(funcMap).ParseFS(qfassets.TemplatesFS, "web/templates/setup.html")
	}
	if err != nil {
		return nil, fmt.Errorf("parsing setup template: %w", err)
	}
	templates["setup.html"] = tmpl

	return &SetupHandler{
		localDB:    localDB,
		connMgr:    connMgr,
		templates:  templates,
		restartSig: restartSig,
	}, nil
}

// ShowSetup renders the database setup form
func (h *SetupHandler) ShowSetup(c *gin.Context) {
	c.Header("Content-Type", "text/html; charset=utf-8")

	// Get existing settings if any
	settings, _ := h.localDB.GetSettings()

	data := gin.H{
		"Settings": settings,
	}

	tmpl := h.templates["setup.html"]
	if err := tmpl.ExecuteTemplate(c.Writer, "setup.html", data); err != nil {
		c.String(http.StatusInternalServerError, "Template error: %v", err)
	}
}

// TestConnection tests the database connection without saving
func (h *SetupHandler) TestConnection(c *gin.Context) {
	host := c.PostForm("host")
	portStr := c.PostForm("port")
	database := c.PostForm("database")
	user := c.PostForm("user")
	password := c.PostForm("password")

	port := 3306
	if p, err := strconv.Atoi(portStr); err == nil {
		port = p
	}

	// If password is empty, try to use saved password
	if password == "" {
		if settings, err := h.localDB.GetSettings(); err == nil && settings != nil {
			password = settings.PasswordEncrypted // GetSettings returns decrypted password in this field
		}
	}

	dsn := buildMySQLDSN(host, port, database, user, password, 5*time.Second)

	db, err := gorm.Open(gormmysql.Open(dsn), &gorm.Config{
		Logger: logger.Default.LogMode(logger.Silent),
	})
	if err != nil {
		c.JSON(http.StatusOK, gin.H{
			"success": false,
			"error":   fmt.Sprintf("Connection failed: %v", err),
		})
		return
	}

	sqlDB, err := db.DB()
	if err != nil {
		c.JSON(http.StatusOK, gin.H{
			"success": false,
			"error":   fmt.Sprintf("Failed to get database handle: %v", err),
		})
		return
	}
	defer sqlDB.Close()

	if err := sqlDB.Ping(); err != nil {
		c.JSON(http.StatusOK, gin.H{
			"success": false,
			"error":   fmt.Sprintf("Ping failed: %v", err),
		})
		return
	}

	// Check for required tables
	var lotCount int64
	if err := db.Table("lot").Count(&lotCount).Error; err != nil {
		c.JSON(http.StatusOK, gin.H{
			"success": false,
			"error":   fmt.Sprintf("Table 'lot' not found or inaccessible: %v", err),
		})
		return
	}

	// Check write permission
	canWrite := testWritePermission(db)

	c.JSON(http.StatusOK, gin.H{
		"success":   true,
		"lot_count": lotCount,
		"can_write": canWrite,
		"message":   fmt.Sprintf("Connected successfully! Found %d components.", lotCount),
	})
}

// SaveConnection saves the connection settings and signals restart
func (h *SetupHandler) SaveConnection(c *gin.Context) {
	existingSettings, _ := h.localDB.GetSettings()

	host := c.PostForm("host")
	portStr := c.PostForm("port")
	database := c.PostForm("database")
	user := c.PostForm("user")
	password := c.PostForm("password")

	port := 3306
	if p, err := strconv.Atoi(portStr); err == nil {
		port = p
	}

	// If password is empty, use saved password
	if password == "" {
		if settings, err := h.localDB.GetSettings(); err == nil && settings != nil {
			password = settings.PasswordEncrypted // GetSettings returns decrypted password in this field
		}
	}

	// Test connection first
	dsn := buildMySQLDSN(host, port, database, user, password, 5*time.Second)

	db, err := gorm.Open(gormmysql.Open(dsn), &gorm.Config{
		Logger: logger.Default.LogMode(logger.Silent),
	})
	if err != nil {
		c.JSON(http.StatusBadRequest, gin.H{
			"success": false,
			"error":   fmt.Sprintf("Connection failed: %v", err),
		})
		return
	}

	sqlDB, _ := db.DB()
	sqlDB.Close()

	// Save settings
	if err := h.localDB.SaveSettings(host, port, database, user, password); err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{
			"success": false,
			"error":   fmt.Sprintf("Failed to save settings: %v", err),
		})
		return
	}

	// Try to connect immediately to verify settings
	if h.connMgr != nil {
		if err := h.connMgr.TryConnect(); err != nil {
			slog.Warn("failed to connect after saving settings", "error", err)
		} else {
			slog.Info("successfully connected to database after saving settings")
		}
	}

	settingsChanged := existingSettings == nil ||
		existingSettings.Host != host ||
		existingSettings.Port != port ||
		existingSettings.Database != database ||
		existingSettings.User != user ||
		existingSettings.PasswordEncrypted != password

	restartQueued := settingsChanged && h.restartSig != nil
	c.JSON(http.StatusOK, gin.H{
		"success":          true,
		"message":          "Settings saved.",
		"restart_required": settingsChanged,
		"restart_queued":   restartQueued,
	})

	// Signal restart after response is sent (if restart signal is configured)
	if restartQueued {
		go func() {
			time.Sleep(500 * time.Millisecond) // Give time for response to be sent
			select {
			case h.restartSig <- struct{}{}:
			default:
			}
		}()
	}
}

// GetStatus returns the current setup status
func (h *SetupHandler) GetStatus(c *gin.Context) {
	hasSettings := h.localDB.HasSettings()
	c.JSON(http.StatusOK, gin.H{
		"configured": hasSettings,
	})
}

func testWritePermission(db *gorm.DB) bool {
	// Simple check: try to create a temporary table and drop it
	testTable := fmt.Sprintf("qt_write_test_%d", time.Now().UnixNano())

	// Try to create a test table
	err := db.Exec(fmt.Sprintf("CREATE TABLE %s (id INT)", testTable)).Error
	if err != nil {
		return false
	}

	// Drop it immediately
	db.Exec(fmt.Sprintf("DROP TABLE %s", testTable))

	return true
}

func buildMySQLDSN(host string, port int, database, user, password string, timeout time.Duration) string {
	cfg := mysqlDriver.NewConfig()
	cfg.User = user
	cfg.Passwd = password
	cfg.Net = "tcp"
	cfg.Addr = net.JoinHostPort(host, strconv.Itoa(port))
	cfg.DBName = database
	cfg.ParseTime = true
	cfg.Loc = time.Local
	cfg.Timeout = timeout
	cfg.Params = map[string]string{
		"charset": "utf8mb4",
	}
	return cfg.FormatDSN()
}
