package web

import (
	"encoding/json"
	"errors"
	"net/http"
	"path/filepath"
	"strings"
	"time"

	"ymhut-box/server/unified-management/internal/config"
	"ymhut-box/server/unified-management/internal/db"
)

type setupRouter struct {
	cfg *config.Config
}

type setupRequest struct {
	Provider   string           `json:"provider"`
	BaseURL    string           `json:"baseUrl"`
	SQLitePath string           `json:"sqlitePath"`
	MySQLDSN   string           `json:"mysqlDsn"`
	MySQL      config.MySQLInput `json:"mysql"`
}

func NewSetupRouter(cfg *config.Config) http.Handler {
	return withSecurity(&setupRouter{cfg: cfg})
}

func (r *setupRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
	path := cleanPath(req.URL.Path)
	switch {
	case path == "/" || path == "/setup":
		r.serveSetup(w, req)
	case strings.HasPrefix(path, "/setup/assets/"):
		serveStaticAsset(w, req, r.cfg.SetupWebDir, "setup/dist", strings.TrimPrefix(path, "/setup/"))
	case path == "/api/setup/status":
		writeJSON(w, http.StatusOK, r.status())
	case path == "/api/setup/database/test":
		r.handleDatabaseTest(w, req)
	case path == "/api/setup/complete":
		r.handleComplete(w, req)
	default:
		if strings.HasPrefix(path, "/api/") {
			writeError(w, http.StatusServiceUnavailable, "SETUP_REQUIRED", errors.New("system setup is required"))
			return
		}
		http.Redirect(w, req, "/setup", http.StatusFound)
	}
}

func (r *setupRouter) status() map[string]any {
	return map[string]any{
		"ok":          true,
		"initialized": r.cfg.Initialized,
		"baseDir":     ".",
		"configPath":  relativeToBase(r.cfg.BaseDir, r.cfg.ConfigPath),
		"defaults": map[string]any{
			"provider":   firstNonEmpty(r.cfg.Database.Provider, "sqlite"),
			"sqlitePath": relativeToBase(r.cfg.BaseDir, r.cfg.Database.SQLitePath),
			"mysqlDsn":   config.MaskDSN(r.cfg.Database.MySQLDSN),
			"baseUrl":    r.cfg.BaseURL,
		},
	}
}

func (r *setupRouter) handleDatabaseTest(w http.ResponseWriter, req *http.Request) {
	if req.Method != http.MethodPost {
		writeError(w, http.StatusMethodNotAllowed, "METHOD_NOT_ALLOWED", errors.New("POST required"))
		return
	}
	next, body, err := r.decodeSetupDatabase(req)
	if err != nil {
		writeError(w, http.StatusBadRequest, "INVALID_PAYLOAD", err)
		return
	}
	started := time.Now()
	if err := db.TestDatabase(next); err != nil {
		writeError(w, http.StatusBadGateway, "DATABASE_TEST_FAILED", err)
		return
	}
	writeJSON(w, http.StatusOK, map[string]any{
		"ok":        true,
		"provider":  next.Provider,
		"latencyMs": time.Since(started).Milliseconds(),
		"maskedDsn": maskedDatabaseTarget(r.cfg.BaseDir, next),
		"normalized": map[string]any{
			"provider":   next.Provider,
			"baseUrl":    firstNonEmpty(body.BaseURL, r.cfg.BaseURL),
			"sqlitePath": relativeToBase(r.cfg.BaseDir, next.SQLitePath),
			"mysqlDsn":   config.MaskDSN(next.MySQLDSN),
		},
	})
}

func (r *setupRouter) handleComplete(w http.ResponseWriter, req *http.Request) {
	if req.Method != http.MethodPost {
		writeError(w, http.StatusMethodNotAllowed, "METHOD_NOT_ALLOWED", errors.New("POST required"))
		return
	}
	nextDB, body, err := r.decodeSetupDatabase(req)
	if err != nil {
		writeError(w, http.StatusBadRequest, "INVALID_PAYLOAD", err)
		return
	}
	if err := db.TestDatabase(nextDB); err != nil {
		writeError(w, http.StatusBadGateway, "DATABASE_TEST_FAILED", err)
		return
	}
	next := *r.cfg
	next.Initialized = true
	next.BaseURL = firstNonEmpty(strings.TrimSpace(body.BaseURL), next.BaseURL)
	next.Database = nextDB
	if strings.EqualFold(next.Database.Provider, "mysql") {
		next.Database.FailoverEnabled = true
		next.Database.HotSyncEnabled = true
	}
	store, err := db.Open(&next)
	if err != nil {
		writeError(w, http.StatusInternalServerError, "DATABASE_OPEN_FAILED", err)
		return
	}
	if err := store.EnsureDefaultAdmin(req.Context()); err != nil {
		_ = store.Close()
		writeError(w, http.StatusInternalServerError, "ADMIN_INIT_FAILED", err)
		return
	}
	_ = store.Close()
	if err := config.Save(&next); err != nil {
		writeError(w, http.StatusInternalServerError, "CONFIG_SAVE_FAILED", err)
		return
	}
	writeJSON(w, http.StatusOK, map[string]any{"ok": true, "initialized": true, "message": "Setup completed. Restart the service, then open /admin/login."})
}

func (r *setupRouter) decodeSetupDatabase(req *http.Request) (config.DatabaseConfig, setupRequest, error) {
	var body setupRequest
	if err := json.NewDecoder(req.Body).Decode(&body); err != nil {
		return config.DatabaseConfig{}, body, err
	}
	incoming := config.DatabaseConfig{
		Provider:      body.Provider,
		SQLitePath:    body.SQLitePath,
		MySQLDSN:      body.MySQLDSN,
		MySQLHost:     body.MySQL.Host,
		MySQLPort:     body.MySQL.Port,
		MySQLDatabase: body.MySQL.Database,
		MySQLUser:     body.MySQL.Username,
		MySQLPassword: body.MySQL.Password,
	}
	next, err := config.NormalizeDatabase(r.cfg.BaseDir, r.cfg.Database, incoming, false)
	return next, body, err
}

func (r *setupRouter) serveSetup(w http.ResponseWriter, req *http.Request) {
	index := filepath.Join(r.cfg.SetupWebDir, "index.html")
	if tryServeDiskFile(w, req, r.cfg.SetupWebDir, "index.html") {
		return
	}
	if serveEmbeddedFile(w, req, "setup/dist/index.html") {
		return
	}
	w.Header().Set("Content-Type", "text/html; charset=utf-8")
	_, _ = w.Write([]byte(`<!doctype html><html lang="zh-CN"><head><meta charset="utf-8"><title>YMhut Setup</title></head><body><main><h1>YMhut Setup</h1><p>Setup frontend is not built. Run npm install && npm run build in web/setup.</p><p>` + index + `</p></main></body></html>`))
}

func maskedDatabaseTarget(base string, cfg config.DatabaseConfig) string {
	if strings.EqualFold(cfg.Provider, "mysql") {
		return config.MaskDSN(cfg.MySQLDSN)
	}
	return relativeToBase(base, cfg.SQLitePath)
}

func relativeToBase(base, value string) string {
	if strings.TrimSpace(value) == "" {
		return ""
	}
	if base != "" {
		if rel, err := filepath.Rel(base, value); err == nil && !strings.HasPrefix(rel, "..") && rel != "." {
			return filepath.ToSlash(rel)
		}
	}
	return filepath.ToSlash(value)
}
