package web

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

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

func (r *router) handleAdminDatabase(w http.ResponseWriter, req *http.Request) {
	path := cleanPath(req.URL.Path)
	switch {
	case req.Method == http.MethodGet && path == "/api/admin/database/config":
		writeJSON(w, http.StatusOK, map[string]any{"ok": true, "config": config.SafeDatabase(r.cfg.BaseDir, r.cfg.Database)})
	case req.Method == http.MethodGet && path == "/api/admin/database/status":
		writeJSON(w, http.StatusOK, map[string]any{"ok": true, "database": r.store.Status(), "config": config.SafeDatabase(r.cfg.BaseDir, r.cfg.Database)})
	case req.Method == http.MethodPost && path == "/api/admin/database/test":
		body, err := decodeAdminDatabaseConfig(req, r.cfg.BaseDir, r.cfg.Database, true)
		if err != nil {
			writeError(w, http.StatusBadRequest, "INVALID_PAYLOAD", err)
			return
		}
		if err := db.TestDatabase(body); err != nil {
			writeError(w, http.StatusBadGateway, "DATABASE_TEST_FAILED", err)
			return
		}
		writeJSON(w, http.StatusOK, map[string]any{"ok": true, "config": config.SafeDatabase(r.cfg.BaseDir, body)})
	case req.Method == http.MethodPost && path == "/api/admin/database/save":
		body, err := decodeAdminDatabaseConfig(req, r.cfg.BaseDir, r.cfg.Database, true)
		if err != nil {
			writeError(w, http.StatusBadRequest, "INVALID_PAYLOAD", err)
			return
		}
		if err := db.TestDatabase(body); err != nil {
			writeError(w, http.StatusBadGateway, "DATABASE_TEST_FAILED", err)
			return
		}
		next := *r.cfg
		next.Database = body
		if err := config.Save(&next); err != nil {
			writeError(w, http.StatusInternalServerError, "DATABASE_SAVE_FAILED", err)
			return
		}
		r.cfg.Database = next.Database
		if err := r.store.ReconfigureDatabase(r.cfg); err != nil {
			writeError(w, http.StatusInternalServerError, "DATABASE_SAVE_FAILED", err)
			return
		}
		_ = r.store.InsertAudit(db.AuditLog{Actor: "admin", Type: "system.database.saved", Target: body.Provider, Message: "数据库配置已保存并热切换", IP: req.RemoteAddr, UserAgent: req.UserAgent()})
		writeJSON(w, http.StatusOK, map[string]any{"ok": true, "database": r.store.Status(), "config": config.SafeDatabase(r.cfg.BaseDir, r.cfg.Database)})
	case req.Method == http.MethodPost && path == "/api/admin/database/import-sqlite":
		result, err := r.store.ImportSQLiteToRemote()
		if err != nil {
			writeError(w, http.StatusBadGateway, "DATABASE_IMPORT_FAILED", err)
			return
		}
		writeJSON(w, http.StatusOK, map[string]any{"ok": true, "result": result})
	case req.Method == http.MethodPost && path == "/api/admin/database/sync":
		result, err := r.store.SyncNow()
		if err != nil {
			writeError(w, http.StatusBadGateway, "DATABASE_SYNC_FAILED", err)
			return
		}
		writeJSON(w, http.StatusOK, map[string]any{"ok": true, "result": result})
	default:
		http.NotFound(w, req)
	}
}

type adminDatabaseRequest struct {
	Provider      string            `json:"provider"`
	SQLitePath    string            `json:"sqlite_path"`
	SQLitePathAlt string            `json:"sqlitePath"`
	MySQLDSN      string            `json:"mysql_dsn"`
	MySQLDSNAlt   string            `json:"mysqlDsn"`
	MySQLHost     string            `json:"mysql_host"`
	MySQLHostAlt  string            `json:"mysqlHost"`
	MySQLPort     int               `json:"mysql_port"`
	MySQLPortAlt  int               `json:"mysqlPort"`
	MySQLDatabase string            `json:"mysql_database"`
	MySQLDBAlt    string            `json:"mysqlDatabase"`
	MySQLUser     string            `json:"mysql_user"`
	MySQLUserAlt  string            `json:"mysqlUser"`
	MySQLPassword string            `json:"mysql_password"`
	MySQLPassAlt  string            `json:"mysqlPassword"`
	MySQL         config.MySQLInput `json:"mysql"`
}

func decodeAdminDatabaseConfig(req *http.Request, baseDir string, current config.DatabaseConfig, keepPassword bool) (config.DatabaseConfig, error) {
	var body adminDatabaseRequest
	if err := json.NewDecoder(req.Body).Decode(&body); err != nil {
		return config.DatabaseConfig{}, err
	}
	incoming := config.DatabaseConfig{
		Provider:      body.Provider,
		SQLitePath:    firstNonEmpty(body.SQLitePath, body.SQLitePathAlt),
		MySQLDSN:      firstNonEmpty(body.MySQLDSN, body.MySQLDSNAlt),
		MySQLHost:     firstNonEmpty(body.MySQLHost, body.MySQLHostAlt, body.MySQL.Host),
		MySQLPort:     firstPositive(body.MySQLPort, body.MySQLPortAlt, body.MySQL.Port),
		MySQLDatabase: firstNonEmpty(body.MySQLDatabase, body.MySQLDBAlt, body.MySQL.Database),
		MySQLUser:     firstNonEmpty(body.MySQLUser, body.MySQLUserAlt, body.MySQL.Username),
		MySQLPassword: firstNonEmpty(body.MySQLPassword, body.MySQLPassAlt, body.MySQL.Password),
	}
	return config.NormalizeDatabase(baseDir, current, incoming, keepPassword)
}

func firstPositive(values ...int) int {
	for _, value := range values {
		if value > 0 {
			return value
		}
	}
	return 0
}

func (r *router) handleAdminDashboard(w http.ResponseWriter, req *http.Request) {
	path := cleanPath(req.URL.Path)
	if req.Method != http.MethodGet || path != "/api/admin/dashboard/overview" {
		http.NotFound(w, req)
		return
	}
	overview, err := r.store.DashboardOverview(80)
	if err != nil {
		writeError(w, http.StatusInternalServerError, "DASHBOARD_FAILED", err)
		return
	}
	overview["health"] = health.Snapshot(r.cfg, r.store)
	writeJSON(w, http.StatusOK, overview)
}

func (r *router) handleAdminSync(w http.ResponseWriter, req *http.Request) {
	if r.syncer == nil {
		writeError(w, http.StatusNotFound, "SYNC_DISABLED", errors.New("legacy sync service is not configured"))
		return
	}
	path := cleanPath(req.URL.Path)
	switch {
	case req.Method == http.MethodGet && path == "/api/admin/sync/legacy/preview":
		writeJSON(w, http.StatusOK, r.syncer.Preview(req.Context()))
	case req.Method == http.MethodPost && path == "/api/admin/sync/legacy/run":
		writeJSON(w, http.StatusOK, r.syncer.Run(req.Context()))
	default:
		http.NotFound(w, req)
	}
}

func (r *router) handleAdminEndpoints(w http.ResponseWriter, req *http.Request) {
	if req.Method != http.MethodGet {
		http.NotFound(w, req)
		return
	}
	items, err := r.sources.Endpoints(true)
	if err != nil {
		writeError(w, http.StatusInternalServerError, "ENDPOINTS_FAILED", err)
		return
	}
	writeJSON(w, http.StatusOK, map[string]any{"ok": true, "items": items})
}

func (r *router) handleAdminEvents(w http.ResponseWriter, req *http.Request) {
	if req.Method != http.MethodGet {
		writeError(w, http.StatusMethodNotAllowed, "METHOD_NOT_ALLOWED", errors.New("GET required"))
		return
	}
	flusher, ok := w.(http.Flusher)
	if !ok {
		writeError(w, http.StatusInternalServerError, "SSE_UNSUPPORTED", errors.New("streaming is not supported"))
		return
	}
	w.Header().Set("Content-Type", "text/event-stream; charset=utf-8")
	w.Header().Set("Cache-Control", "no-cache")
	w.Header().Set("Connection", "keep-alive")
	events, unsubscribe := r.sources.SubscribeEvents()
	defer unsubscribe()
	ticker := time.NewTicker(15 * time.Second)
	defer ticker.Stop()
	writeSSE(w, "ready", map[string]any{"ok": true, "time": time.Now().UTC().Format(time.RFC3339)})
	flusher.Flush()
	for {
		select {
		case event, ok := <-events:
			if !ok {
				return
			}
			writeSSE(w, event.Type, event.Data)
			flusher.Flush()
		case <-ticker.C:
			writeSSE(w, "heartbeat", map[string]any{"time": time.Now().UTC().Format(time.RFC3339)})
			flusher.Flush()
		case <-req.Context().Done():
			return
		}
	}
}

func (r *router) handleAdminSystem(w http.ResponseWriter, req *http.Request) {
	path := cleanPath(req.URL.Path)
	switch path {
	case "/api/admin/system/health":
		writeJSON(w, http.StatusOK, health.Snapshot(r.cfg, r.store))
	case "/api/admin/system/audit":
		page, err := r.store.ListAuditLogsPage(db.AuditFilters{
			Page:    queryInt(req, "page", 1),
			PerPage: queryInt(req, "perPage", 35),
			Type:    req.URL.Query().Get("type"),
			Target:  req.URL.Query().Get("target"),
			Query:   req.URL.Query().Get("q"),
		})
		if err != nil {
			writeError(w, http.StatusInternalServerError, "AUDIT_FAILED", err)
			return
		}
		writeJSON(w, http.StatusOK, map[string]any{"ok": true, "items": page.Items, "page": page})
	case "/api/admin/system/mail/config":
		r.handleMailConfig(w, req)
	case "/api/admin/system/mail/test":
		r.handleMailTest(w, req)
	case "/api/admin/system/branding":
		r.handleBranding(w, req)
	case "/api/admin/system/migration":
		r.handleMigrationStatus(w, req)
	case "/api/admin/system/database/sync":
		if req.Method != http.MethodPost {
			writeError(w, http.StatusMethodNotAllowed, "METHOD_NOT_ALLOWED", errors.New("POST required"))
			return
		}
		result, err := r.store.ImportSQLiteToRemote()
		if err != nil {
			writeError(w, http.StatusBadRequest, "SYNC_FAILED", err)
			return
		}
		writeJSON(w, http.StatusOK, map[string]any{"ok": true, "result": result, "finishedAt": result.FinishedAt})
	default:
		http.NotFound(w, req)
	}
}

func queryInt(req *http.Request, key string, fallback int) int {
	value, err := strconv.Atoi(req.URL.Query().Get(key))
	if err != nil || value <= 0 {
		return fallback
	}
	return value
}

func (r *router) handleMigrationStatus(w http.ResponseWriter, req *http.Request) {
	if req.Method != http.MethodGet {
		writeError(w, http.StatusMethodNotAllowed, "METHOD_NOT_ALLOWED", errors.New("GET required"))
		return
	}
	status := r.store.Status()
	writeJSON(w, http.StatusOK, map[string]any{
		"ok": true,
		"migration": map[string]any{
			"strategy": "database_first_with_file_assets",
			"databaseCovers": []string{
				"系统设置与品牌",
				"管理员与会话元数据",
				"反馈工单、附件元数据与邮件记录",
				"来源目录、客户端接口与健康记录",
				"发布元数据、版本公告与兼容 JSON 修订",
				"审计日志、旧项目同步记录与数据库同步状态",
			},
			"fileAssets": []map[string]string{
				{"name": "downloads", "path": r.cfg.DownloadsDir, "description": "发布包和下载文件"},
				{"name": "update public", "path": r.cfg.UpdatePublicDir, "description": "旧客户端兼容 JSON 生成物"},
				{"name": "feedback packages", "path": filepath.Join(r.cfg.StorageDir, "feedback-packages"), "description": "反馈附件包"},
			},
			"sqlitePath":     r.store.Path(),
			"mysql":          config.SafeDatabase(r.cfg.BaseDir, r.cfg.Database),
			"lastSyncAt":     status.LastSyncAt,
			"lastSyncError":  status.LastSyncError,
			"activeProvider": status.ActiveProvider,
		},
	})
}

func (r *router) handleBranding(w http.ResponseWriter, req *http.Request) {
	switch req.Method {
	case http.MethodGet:
		writeJSON(w, http.StatusOK, map[string]any{"ok": true, "branding": config.SafeBranding(r.effectiveBranding())})
	case http.MethodPost:
		var body struct {
			SiteIconURL        string `json:"siteIconUrl"`
			SiteIconURLSnake   string `json:"site_icon_url"`
			DeveloperAvatarURL string `json:"developerAvatarUrl"`
			DeveloperAvatarAlt string `json:"developer_avatar_url"`
			DeveloperName      string `json:"developerName"`
			DeveloperNameSnake string `json:"developer_name"`
			FeedbackEmail      string `json:"feedbackEmail"`
			FeedbackEmailSnake string `json:"feedback_email"`
		}
		if err := json.NewDecoder(req.Body).Decode(&body); err != nil {
			writeError(w, http.StatusBadRequest, "INVALID_PAYLOAD", err)
			return
		}
		next := config.BrandingConfig{
			SiteIconURL:        firstNonEmpty(body.SiteIconURL, body.SiteIconURLSnake),
			DeveloperAvatarURL: firstNonEmpty(body.DeveloperAvatarURL, body.DeveloperAvatarAlt),
			DeveloperName:      firstNonEmpty(body.DeveloperName, body.DeveloperNameSnake),
			FeedbackEmail:      firstNonEmpty(body.FeedbackEmail, body.FeedbackEmailSnake),
		}
		if err := r.saveBranding(next); err != nil {
			writeError(w, http.StatusInternalServerError, "BRANDING_SAVE_FAILED", err)
			return
		}
		_ = r.store.InsertAudit(db.AuditLog{Actor: "admin", Type: "system.branding.saved", Target: r.cfg.Branding.DeveloperName, Message: "站点品牌信息已保存", IP: req.RemoteAddr, UserAgent: req.UserAgent()})
		writeJSON(w, http.StatusOK, map[string]any{"ok": true, "branding": config.SafeBranding(r.effectiveBranding())})
	default:
		writeError(w, http.StatusMethodNotAllowed, "METHOD_NOT_ALLOWED", errors.New("GET or POST required"))
	}
}

func (r *router) handleMailConfig(w http.ResponseWriter, req *http.Request) {
	switch req.Method {
	case http.MethodGet:
		writeJSON(w, http.StatusOK, map[string]any{"ok": true, "config": feedbackmail.SafeConfig(r.cfg.Mail)})
	case http.MethodPost:
		nextMail, err := decodeMailConfig(req, r.cfg.Mail)
		if err != nil {
			writeError(w, http.StatusBadRequest, "INVALID_PAYLOAD", err)
			return
		}
		next := *r.cfg
		next.Mail = nextMail
		if err := config.Save(&next); err != nil {
			writeError(w, http.StatusInternalServerError, "MAIL_CONFIG_FAILED", err)
			return
		}
		r.cfg.Mail = next.Mail
		_ = r.store.InsertAudit(db.AuditLog{Actor: "admin", Type: "system.mail.saved", Target: nextMail.Host, Message: "邮件通知配置已保存", IP: req.RemoteAddr, UserAgent: req.UserAgent()})
		writeJSON(w, http.StatusOK, map[string]any{"ok": true, "config": feedbackmail.SafeConfig(r.cfg.Mail)})
	default:
		writeError(w, http.StatusMethodNotAllowed, "METHOD_NOT_ALLOWED", errors.New("GET or POST required"))
	}
}

func (r *router) handleMailTest(w http.ResponseWriter, req *http.Request) {
	if req.Method != http.MethodPost {
		writeError(w, http.StatusMethodNotAllowed, "METHOD_NOT_ALLOWED", errors.New("POST required"))
		return
	}
	message, err := feedbackmail.BuildTestMessage(r.cfg)
	if err != nil {
		writeError(w, http.StatusBadRequest, "MAIL_TEST_FAILED", err)
		return
	}
	if err := feedbackmail.Send(r.cfg, message); err != nil {
		writeError(w, http.StatusBadGateway, "MAIL_TEST_FAILED", err)
		return
	}
	_ = r.store.InsertAudit(db.AuditLog{Actor: "admin", Type: "system.mail.test", Target: message.To, Message: "测试邮件已发送", IP: req.RemoteAddr, UserAgent: req.UserAgent()})
	writeJSON(w, http.StatusOK, map[string]any{"ok": true})
}

type mailConfigRequest struct {
	Host             string `json:"host"`
	Port             int    `json:"port"`
	Secure           string `json:"secure"`
	Username         string `json:"username"`
	Password         string `json:"password"`
	FromAddress      string `json:"from_address"`
	FromAddressAlt   string `json:"fromAddress"`
	FromName         string `json:"from_name"`
	FromNameAlt      string `json:"fromName"`
	DeveloperAddress string `json:"developer_address"`
	DeveloperAlt     string `json:"developerAddress"`
	TimeoutSeconds   int    `json:"timeout_seconds"`
	TimeoutAlt       int    `json:"timeoutSeconds"`
}

func decodeMailConfig(req *http.Request, current config.MailConfig) (config.MailConfig, error) {
	var body mailConfigRequest
	if err := json.NewDecoder(req.Body).Decode(&body); err != nil {
		return config.MailConfig{}, err
	}
	next := current
	if body.Host != "" {
		next.Host = body.Host
	}
	if body.Port > 0 {
		next.Port = body.Port
	}
	if body.Secure != "" {
		next.Secure = body.Secure
	}
	if body.Username != "" {
		next.Username = body.Username
	}
	if body.Password != "" {
		next.Password = body.Password
	}
	if value := firstNonEmpty(body.FromAddress, body.FromAddressAlt); value != "" {
		next.FromAddress = value
	}
	if value := firstNonEmpty(body.FromName, body.FromNameAlt); value != "" {
		next.FromName = value
	}
	if value := firstNonEmpty(body.DeveloperAddress, body.DeveloperAlt); value != "" {
		next.DeveloperAddress = value
	}
	if timeout := firstPositive(body.TimeoutSeconds, body.TimeoutAlt); timeout > 0 {
		next.TimeoutSeconds = timeout
	}
	if next.Port <= 0 {
		next.Port = 465
	}
	if next.Secure == "" {
		next.Secure = "ssl"
	}
	if next.FromName == "" {
		next.FromName = "YMhut Box Feedback"
	}
	if next.FromAddress == "" {
		next.FromAddress = next.Username
	}
	if next.TimeoutSeconds <= 0 {
		next.TimeoutSeconds = 20
	}
	return next, nil
}
