package web

import (
	"encoding/json"
	"errors"
	"net/http"
	"strings"

	"ymhut-box/server/unified-management/internal/auth"
	"ymhut-box/server/unified-management/internal/config"
	"ymhut-box/server/unified-management/internal/db"
	"ymhut-box/server/unified-management/internal/feedback"
	"ymhut-box/server/unified-management/internal/legacy"
	"ymhut-box/server/unified-management/internal/notices"
	"ymhut-box/server/unified-management/internal/releases"
	"ymhut-box/server/unified-management/internal/sources"
	"ymhut-box/server/unified-management/internal/synclegacy"
)

type router struct {
	cfg      *config.Config
	store    *db.Store
	auth     *auth.Service
	feedback *feedback.Service
	releases *releases.Service
	sources  *sources.Service
	legacy   *legacy.Service
	notices  *notices.Service
	syncer   *synclegacy.Service
}

func NewRouter(cfg *config.Config, store *db.Store, authService *auth.Service, feedbackService *feedback.Service, releaseService *releases.Service, sourceService *sources.Service, legacyService *legacy.Service, optional ...any) http.Handler {
	r := &router{
		cfg:      cfg,
		store:    store,
		auth:     authService,
		feedback: feedbackService,
		releases: releaseService,
		sources:  sourceService,
		legacy:   legacyService,
	}
	for _, item := range optional {
		switch typed := item.(type) {
		case *notices.Service:
			r.notices = typed
		case *synclegacy.Service:
			r.syncer = typed
		}
	}
	return withSecurity(r)
}

func (r *router) ServeHTTP(w http.ResponseWriter, req *http.Request) {
	path := cleanPath(req.URL.Path)
	switch {
	case path == "/" && req.Method == http.MethodPost:
		r.handleFeedbackSubmit(w, req)
	case path == "/" && req.URL.Query().Get("api") == "status":
		r.handleFeedbackStatus(w, req)
	case isPortalRoute(path):
		r.servePortal(w, req)
	case path == "/api/auth/bootstrap" || path == "/api/admin/auth/bootstrap":
		r.handleAuthBootstrap(w, req)
	case path == "/api/auth/captcha" || path == "/api/admin/auth/captcha":
		r.handleCaptcha(w, req)
	case path == "/api/auth/login" || path == "/api/admin/auth/login":
		r.handleLogin(w, req)
	case path == "/api/auth/logout" || path == "/api/admin/auth/logout":
		r.auth.Require(http.HandlerFunc(r.handleLogout)).ServeHTTP(w, req)
	case path == "/api/admin/auth/password":
		r.auth.Require(http.HandlerFunc(r.handleChangePassword)).ServeHTTP(w, req)
	case path == "/api/client/bootstrap":
		r.handleClientBootstrap(w, req)
	case path == "/api/client/releases" || path == "/api/releases" || path == "/api/update-info":
		writeJSON(w, http.StatusOK, r.releases.Manifest(req))
	case path == "/api/client/sources":
		r.handleClientSources(w, req)
	case path == "/api/client/endpoints":
		r.handleClientEndpoints(w, req)
	case path == "/api/client/notices" || strings.HasPrefix(path, "/api/client/notices/"):
		r.handleClientNotices(w, req)
	case path == "/api/client/endpoint-calls" || path == "/api/client/source-calls":
		r.handleSourceCall(w, req)
	case path == "/update-info.json" || path == "/update-info":
		writeJSON(w, http.StatusOK, r.releases.LegacyUpdateInfo(req))
	case path == "/tool-status.json" || path == "/tool-status":
		writeJSON(w, http.StatusOK, r.releases.StaticJSON("tool-status.json"))
	case path == "/modules.json" || path == "/modules" || path == "/api/modules":
		writeJSON(w, http.StatusOK, r.releases.StaticJSON("modules.json"))
	case path == "/media-types.json" || path == "/media-types":
		r.handleLegacyMediaTypes(w, req)
	case strings.HasPrefix(path, "/downloads/"):
		r.handleDownload(w, req)
	case strings.HasPrefix(path, "/admin/assets/"):
		serveStaticAsset(w, req, r.cfg.AdminWebDir, "admin/dist", strings.TrimPrefix(path, "/admin/"))
	case strings.HasPrefix(path, "/assets/"):
		serveStaticAsset(w, req, r.cfg.PortalWebDir, "portal/dist", strings.TrimPrefix(path, "/"))
	case strings.HasPrefix(path, "/api/admin/feedbacks"):
		r.auth.Require(http.HandlerFunc(r.handleAdminFeedbacks)).ServeHTTP(w, req)
	case strings.HasPrefix(path, "/api/admin/dashboard"):
		r.auth.Require(http.HandlerFunc(r.handleAdminDashboard)).ServeHTTP(w, req)
	case strings.HasPrefix(path, "/api/admin/sync"):
		r.auth.Require(http.HandlerFunc(r.handleAdminSync)).ServeHTTP(w, req)
	case strings.HasPrefix(path, "/api/admin/releases"):
		r.auth.Require(http.HandlerFunc(r.handleAdminReleases)).ServeHTTP(w, req)
	case strings.HasPrefix(path, "/api/admin/sources"):
		r.auth.Require(http.HandlerFunc(r.handleAdminSources)).ServeHTTP(w, req)
	case strings.HasPrefix(path, "/api/admin/endpoints"):
		r.auth.Require(http.HandlerFunc(r.handleAdminEndpoints)).ServeHTTP(w, req)
	case path == "/api/admin/events":
		r.auth.Require(http.HandlerFunc(r.handleAdminEvents)).ServeHTTP(w, req)
	case strings.HasPrefix(path, "/api/admin/legacy"):
		r.auth.Require(http.HandlerFunc(r.handleAdminLegacy)).ServeHTTP(w, req)
	case strings.HasPrefix(path, "/api/admin/database"):
		r.auth.Require(http.HandlerFunc(r.handleAdminDatabase)).ServeHTTP(w, req)
	case strings.HasPrefix(path, "/api/admin/system"):
		r.auth.Require(http.HandlerFunc(r.handleAdminSystem)).ServeHTTP(w, req)
	case path == "/admin" || path == "/admin/":
		http.Redirect(w, req, "/admin/dashboard", http.StatusFound)
	case path == "/admin/login" || strings.HasPrefix(path, "/admin/"):
		r.serveAdmin(w, req)
	default:
		http.NotFound(w, req)
	}
}

func (r *router) handleAuthBootstrap(w http.ResponseWriter, req *http.Request) {
	payload, err := r.auth.Bootstrap(req.Context())
	if err != nil {
		writeError(w, http.StatusInternalServerError, "BOOTSTRAP_FAILED", err)
		return
	}
	writeJSON(w, http.StatusOK, payload)
}

func (r *router) handleCaptcha(w http.ResponseWriter, req *http.Request) {
	captcha, err := r.auth.NewCaptcha()
	if err != nil {
		writeError(w, http.StatusInternalServerError, "CAPTCHA_FAILED", err)
		return
	}
	writeJSON(w, http.StatusOK, map[string]any{"ok": true, "captchaId": captcha.ID, "image": captcha.Image})
}

func (r *router) handleLogin(w http.ResponseWriter, req *http.Request) {
	if req.Method != http.MethodPost {
		writeError(w, http.StatusMethodNotAllowed, "METHOD_NOT_ALLOWED", errors.New("POST required"))
		return
	}
	var body struct {
		Username  string `json:"username"`
		Password  string `json:"password"`
		CaptchaID string `json:"captchaId"`
		Captcha   string `json:"captcha"`
	}
	if err := json.NewDecoder(req.Body).Decode(&body); err != nil {
		writeError(w, http.StatusBadRequest, "INVALID_PAYLOAD", err)
		return
	}
	if body.Username == "" {
		body.Username = "admin"
	}
	sessionID, csrf, ok, err := r.auth.Login(req.Context(), body.Username, body.Password, body.CaptchaID, body.Captcha, req.RemoteAddr)
	if err != nil {
		writeError(w, http.StatusInternalServerError, "LOGIN_FAILED", err)
		return
	}
	if !ok {
		writeError(w, http.StatusOK, "LOGIN_FAILED", errors.New("invalid password or captcha"))
		return
	}
	auth.SetSessionCookieForRequest(w, req, sessionID)
	_ = r.store.InsertAudit(db.AuditLog{Actor: body.Username, Type: "auth.login", Target: "admin", Message: "管理员登录", IP: req.RemoteAddr, UserAgent: req.UserAgent()})
	writeJSON(w, http.StatusOK, map[string]any{"ok": true, "csrfToken": csrf, "user": map[string]any{"username": body.Username}})
}

func (r *router) handleLogout(w http.ResponseWriter, req *http.Request) {
	r.auth.Logout(w, req)
	writeJSON(w, http.StatusOK, map[string]any{"ok": true})
}

func (r *router) handleChangePassword(w http.ResponseWriter, req *http.Request) {
	var body struct {
		CurrentPassword string `json:"currentPassword"`
		NewPassword     string `json:"newPassword"`
	}
	if err := json.NewDecoder(req.Body).Decode(&body); err != nil {
		writeError(w, http.StatusBadRequest, "INVALID_PAYLOAD", err)
		return
	}
	warning, err := r.store.ChangeAdminPasswordWithWarning(req.Context(), "admin", body.CurrentPassword, body.NewPassword)
	if err != nil {
		writeError(w, http.StatusBadRequest, "PASSWORD_CHANGE_FAILED", err)
		return
	}
	_ = r.store.InsertAudit(db.AuditLog{Actor: "admin", Type: "auth.password_changed", Target: "admin", Message: "后台密码已修改", IP: req.RemoteAddr, UserAgent: req.UserAgent()})
	payload := map[string]any{"ok": true, "isDefaultPassword": false}
	if warning != "" {
		payload["warning"] = warning
	}
	writeJSON(w, http.StatusOK, payload)
}
