package app

import (
	"crypto/sha256"
	"crypto/subtle"
	"database/sql"
	"encoding/json"
	"fmt"
	"log"
	"net/http"
	"strings"

	"lingniu/vehicle-data-platform/apps/api/internal/config"
	"lingniu/vehicle-data-platform/apps/api/internal/httpx"
	"lingniu/vehicle-data-platform/apps/api/internal/platform"
)

type configuredPrincipal struct {
	Token string `json:"token"`
	Name  string `json:"name"`
	Role  string `json:"role"`
}

type tokenPrincipal struct {
	hash      [sha256.Size]byte
	principal platform.Principal
}

type apiAuthenticator struct {
	mode     string
	tokens   []tokenPrincipal
	local    *authStore
	adapters []IdentityAdapter
}

func withAPIAuth(next http.Handler, cfg config.Config, databases ...*sql.DB) http.Handler {
	var db *sql.DB
	if len(databases) > 0 {
		db = databases[0]
	}
	authenticator, err := newAPIAuthenticator(cfg, db)
	if err != nil {
		log.Printf("platform API authentication misconfigured: %v", err)
		return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
			httpx.WriteError(w, http.StatusServiceUnavailable, "AUTH_CONFIG_INVALID", "平台鉴权配置无效", err.Error(), requestTraceID(r))
		})
	}
	return authenticator.middleware(next)
}

func newAPIAuthenticator(cfg config.Config, db *sql.DB) (*apiAuthenticator, error) {
	mode := strings.ToLower(strings.TrimSpace(cfg.AuthMode))
	if mode == "" {
		mode = "disabled"
	}
	if mode != "disabled" && mode != "enforce" {
		return nil, fmt.Errorf("AUTH_MODE must be disabled or enforce")
	}
	local, err := newAuthStore(db, cfg)
	if err != nil {
		return nil, err
	}
	authenticator := &apiAuthenticator{mode: mode, local: local}
	configured := []configuredPrincipal{}
	if strings.TrimSpace(cfg.AuthTokensJSON) != "" {
		if err := json.Unmarshal([]byte(cfg.AuthTokensJSON), &configured); err != nil {
			return nil, fmt.Errorf("decode AUTH_TOKENS_JSON: %w", err)
		}
	}
	if token := strings.TrimSpace(cfg.AuthToken); token != "" {
		configured = append(configured, configuredPrincipal{Token: token, Name: "platform-admin", Role: "admin"})
	}
	seen := map[[sha256.Size]byte]bool{}
	for _, item := range configured {
		item.Token = strings.TrimSpace(item.Token)
		item.Name = strings.TrimSpace(item.Name)
		item.Role = strings.ToLower(strings.TrimSpace(item.Role))
		if len(item.Token) < 16 {
			return nil, fmt.Errorf("token for %q must contain at least 16 characters", item.Name)
		}
		if item.Name == "" || roleRank(item.Role) == 0 {
			return nil, fmt.Errorf("token principal requires name and viewer/operator/admin role")
		}
		hash := sha256.Sum256([]byte(item.Token))
		if seen[hash] {
			return nil, fmt.Errorf("duplicate authentication token")
		}
		seen[hash] = true
		principal := platform.Principal{Name: item.Name, Username: item.Name, Role: item.Role, UserType: item.Role, AuthProvider: "legacy-token", MenuKeys: append([]string(nil), adminMenus...)}
		authenticator.tokens = append(authenticator.tokens, tokenPrincipal{hash: hash, principal: principal})
	}
	if mode == "enforce" && len(authenticator.tokens) == 0 && authenticator.local == nil && len(authenticator.adapters) == 0 {
		return nil, fmt.Errorf("enforce mode requires a local identity store, identity adapter, AUTH_TOKEN, or AUTH_TOKENS_JSON")
	}
	return authenticator, nil
}

func (a *apiAuthenticator) middleware(next http.Handler) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		if r.URL.Path == "/api/v2/auth/login" {
			if a.local == nil {
				httpx.WriteError(w, http.StatusServiceUnavailable, "LOCAL_AUTH_UNAVAILABLE", "账号登录尚未启用", "", requestTraceID(r))
				return
			}
			if r.Method != http.MethodPost {
				httpx.WriteError(w, http.StatusMethodNotAllowed, "METHOD_NOT_ALLOWED", "登录接口仅支持 POST", "", requestTraceID(r))
				return
			}
			a.local.login(w, r)
			return
		}
		principal, ok := a.authenticate(r)
		if !ok {
			w.Header().Set("WWW-Authenticate", `Bearer realm="lingniu-vehicle-platform"`)
			httpx.WriteError(w, http.StatusUnauthorized, "AUTH_REQUIRED", "需要有效的访问令牌", "", requestTraceID(r))
			return
		}
		if principal.UserType == "customer" && r.URL.Path != "/api/v2/session" && r.URL.Path != "/api/v2/auth/logout" && r.URL.Path != "/api/v2/auth/password" {
			menu := requiredMenu(r)
			allowed := principal.CanMenu(menu)
			if menu == "shared" {
				allowed = principal.CanMenu("monitor") || principal.CanMenu("vehicles") || principal.CanMenu("tracks") || principal.CanMenu("statistics")
			}
			if menu == "" || !allowed {
				httpx.WriteError(w, http.StatusForbidden, "MENU_PERMISSION_DENIED", "当前账号无权访问此功能", menu, requestTraceID(r))
				return
			}
		}
		required := requiredRole(r)
		if roleRank(principal.Role) < roleRank(required) {
			httpx.WriteError(w, http.StatusForbidden, "PERMISSION_DENIED", "当前角色无权执行该操作", "需要 "+required+" 角色", requestTraceID(r))
			return
		}
		ctx := platform.WithPrincipal(r.Context(), principal)
		r = r.WithContext(ctx)
		if r.URL.Path == "/api/v2/session" {
			principal.VehicleCount = len(principal.VehicleVINs)
			httpx.WriteOK(w, requestTraceID(r), struct {
				platform.Principal
				AuthMode string `json:"authMode"`
			}{principal, a.mode})
			return
		}
		if r.URL.Path == "/api/v2/auth/logout" {
			if r.Method != http.MethodPost {
				httpx.WriteError(w, http.StatusMethodNotAllowed, "METHOD_NOT_ALLOWED", "退出接口仅支持 POST", "", requestTraceID(r))
				return
			}
			if a.local != nil && principal.AuthProvider == "local" {
				a.local.logout(r.Context(), bearerToken(r))
			}
			httpx.WriteOK(w, requestTraceID(r), map[string]bool{"loggedOut": true})
			return
		}
		if r.URL.Path == "/api/v2/auth/password" {
			if r.Method != http.MethodPut || a.local == nil {
				httpx.WriteError(w, http.StatusMethodNotAllowed, "METHOD_NOT_ALLOWED", "密码修改接口仅支持 PUT", "", requestTraceID(r))
				return
			}
			a.local.changePassword(w, r, principal)
			return
		}
		if a.local != nil && a.local.handleAdmin(w, r, principal) {
			return
		}
		next.ServeHTTP(w, r)
	})
}

func (a *apiAuthenticator) authenticate(r *http.Request) (platform.Principal, bool) {
	if a.mode == "disabled" {
		return platform.Principal{Name: "local-developer", Username: "local-developer", Role: "admin", UserType: "admin", AuthProvider: "disabled", MenuKeys: append([]string(nil), adminMenus...)}, true
	}
	token := bearerToken(r)
	if token == "" {
		return platform.Principal{}, false
	}
	if a.local != nil {
		if principal, ok := a.local.authenticate(r.Context(), token); ok {
			return principal, true
		}
	}
	for _, adapter := range a.adapters {
		principal, matched, err := adapter.AuthenticateBearer(r.Context(), token)
		if err != nil {
			log.Printf("identity adapter %s rejected credential: %v", adapter.Name(), err)
			continue
		}
		if matched {
			return principal, true
		}
	}
	hash := sha256.Sum256([]byte(token))
	var match platform.Principal
	matched := 0
	for _, item := range a.tokens {
		equal := subtle.ConstantTimeCompare(hash[:], item.hash[:])
		matched |= equal
		if equal == 1 {
			match = item.principal
		}
	}
	return match, matched == 1
}

func bearerToken(r *http.Request) string {
	header := strings.TrimSpace(r.Header.Get("Authorization"))
	if len(header) < 8 || !strings.EqualFold(header[:7], "Bearer ") {
		return ""
	}
	return strings.TrimSpace(header[7:])
}

func requiredMenu(r *http.Request) string {
	path := r.URL.Path
	switch {
	case strings.HasPrefix(path, "/api/v2/monitor"), path == "/api/v2/alerts/events":
		return "monitor"
	case path == "/api/map/reverse-geocode", path == "/api/realtime/vehicles", path == "/api/realtime/locations", path == "/api/vehicle-service", path == "/api/vehicle-service/overview", strings.HasSuffix(path, "/telemetry/latest"):
		return "shared"
	case path == "/api/v2/tracks":
		return "tracks"
	case path == "/api/mileage/daily", path == "/api/mileage/summary", path == "/api/v2/statistics/mileage", path == "/api/vehicles/coverage", path == "/api/vehicles/coverage/summary":
		return "statistics"
	case path == "/api/vehicles", path == "/api/vehicles/resolve":
		return "shared"
	case strings.HasPrefix(path, "/api/v2/vehicles/"):
		return "vehicles"
	default:
		return ""
	}
}

func requiredRole(r *http.Request) string {
	if strings.HasPrefix(r.URL.Path, "/api/v2/operations/") {
		if r.Method == http.MethodGet || r.Method == http.MethodHead {
			return "operator"
		}
		return "admin"
	}
	if (r.Method == http.MethodGet || r.Method == http.MethodHead) && strings.HasPrefix(r.URL.Path, "/api/v2/exports") {
		return "viewer"
	}
	if r.Method == http.MethodGet || r.Method == http.MethodHead {
		return "viewer"
	}
	path := r.URL.Path
	if r.Method == http.MethodPost {
		switch path {
		case "/api/vehicle-service/overviews", "/api/history/raw-frames/query", "/api/v2/access/summary", "/api/v2/access/vehicles", "/api/v2/alerts/summary", "/api/v2/alerts/events", "/api/v2/exports":
			return "viewer"
		case "/api/v2/alerts/notifications/read":
			return "operator"
		}
		if strings.HasPrefix(path, "/api/v2/alerts/events/") && strings.HasSuffix(path, "/actions") {
			return "operator"
		}
		if path == "/api/v2/alerts/rules" {
			return "admin"
		}
	}
	if r.Method == http.MethodPut && (path == "/api/v2/access/thresholds" || strings.HasPrefix(path, "/api/v2/alerts/rules/")) {
		return "admin"
	}
	if r.Method == http.MethodPut && path == "/api/v2/auth/password" {
		return "viewer"
	}
	return "admin"
}

func roleRank(role string) int {
	switch strings.ToLower(role) {
	case "viewer", "customer":
		return 1
	case "operator":
		return 2
	case "admin":
		return 3
	}
	return 0
}
