package api

import (
	"context"
	"encoding/json"
	"net/http"
	"strings"
	"time"

	"clicd/internal/config"

	"github.com/golang-jwt/jwt/v5"
	"golang.org/x/crypto/bcrypt"
)

type LoginRequest struct {
	Username string `json:"username"`
	Password string `json:"password"`
}

type LoginResponse struct {
	Token    string `json:"token"`
	Username string `json:"username"`
}

type APIResponse struct {
	Success bool        `json:"success"`
	Message string      `json:"message,omitempty"`
	Data    interface{} `json:"data,omitempty"`
}

type authContextKey struct{}

type AuthContext struct {
	Type           string
	Username       string
	ApiKeyID       string
	ApiKeyName     string
	Actor          string
	Scopes         []string
	ContainerUUIDs []string
}

const (
	authTypeAdmin   = "admin"
	authTypeSubUser = "sub_user"
	authTypeAPIKey  = "api_key"
)

func withAuthContext(r *http.Request, auth AuthContext) *http.Request {
	return r.WithContext(context.WithValue(r.Context(), authContextKey{}, auth))
}

func authContextFromRequest(r *http.Request) (AuthContext, bool) {
	ctx, ok := r.Context().Value(authContextKey{}).(AuthContext)
	return ctx, ok
}

func requestActor(r *http.Request) string {
	if ctx, ok := authContextFromRequest(r); ok && ctx.Actor != "" {
		return ctx.Actor
	}
	if claims, ok := claimsFromRequest(r); ok {
		if subUser, _ := claims["sub_user"].(string); subUser != "" {
			return "user:" + subUser
		}
		if username, _ := claims["username"].(string); username != "" {
			return username
		}
	}
	return "admin"
}

func hasScope(r *http.Request, scope string) bool {
	ctx, ok := authContextFromRequest(r)
	if !ok {
		return true
	}
	switch ctx.Type {
	case authTypeAdmin:
		return true
	case authTypeSubUser:
		return subUserScopeAllowed(scope)
	case authTypeAPIKey:
		return scopeAllowed(ctx.Scopes, scope)
	default:
		return false
	}
}

func subUserScopeAllowed(scope string) bool {
	switch scope {
	case "container:read", "container:power", "container:reinstall", "container:password", "container:network",
		"dashboard:read", "image:read", "task:read", "snapshot:read", "snapshot:create", "snapshot:delete", "snapshot:restore", "snapshot:schedule",
		"terminal:ssh", "terminal:vnc":
		return true
	default:
		return false
	}
}

func hasAnyScope(r *http.Request, scopes ...string) bool {
	for _, scope := range scopes {
		if hasScope(r, scope) {
			return true
		}
	}
	return false
}

func scopeAllowed(scopes []string, required string) bool {
	for _, scope := range scopes {
		scope = strings.TrimSpace(scope)
		if scope == "*" || scope == "admin:*" || scope == required {
			return true
		}
		if strings.HasSuffix(scope, ":*") {
			prefix := strings.TrimSuffix(scope, "*")
			if strings.HasPrefix(required, prefix) {
				return true
			}
		}
	}
	return false
}

func requireScope(w http.ResponseWriter, r *http.Request, scope string) bool {
	if hasScope(r, scope) {
		return true
	}
	jsonResponse(w, http.StatusForbidden, APIResponse{Success: false, Message: "Insufficient API key scope"})
	return false
}

func ScopeMiddleware(scope string, next http.HandlerFunc) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		if !requireScope(w, r, scope) {
			return
		}
		next(w, r)
	}
}

func AnyScopeMiddleware(scopes []string, next http.HandlerFunc) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		if hasAnyScope(r, scopes...) {
			next(w, r)
			return
		}
		jsonResponse(w, http.StatusForbidden, APIResponse{Success: false, Message: "Insufficient API key scope"})
	}
}

func auditRequest(r *http.Request, action, target, detail string, success bool, errMsg string) {
	config.AddAuditLogFull(action, target, detail, requestActor(r), clientIP(r), r.UserAgent(), success, errMsg)
}

func jsonResponse(w http.ResponseWriter, status int, resp APIResponse) {
	w.Header().Set("Content-Type", "application/json")
	w.WriteHeader(status)
	json.NewEncoder(w).Encode(resp)
}

func tokenFromRequest(r *http.Request) string {
	authHeader := r.Header.Get("Authorization")
	if strings.HasPrefix(authHeader, "Bearer ") {
		return strings.TrimPrefix(authHeader, "Bearer ")
	}

	cookie, err := r.Cookie("clicd_token")
	if err == nil {
		return cookie.Value
	}

	return ""
}

func isValidToken(tokenString string) bool {
	_, ok := claimsFromToken(tokenString)
	return ok
}

func claimsFromToken(tokenString string) (jwt.MapClaims, bool) {
	if tokenString == "" {
		return nil, false
	}
	token, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) {
		if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
			return nil, jwt.ErrSignatureInvalid
		}
		return []byte(config.AppConfig.JWTSecret), nil
	})
	if err != nil || !token.Valid {
		return nil, false
	}
	claims, ok := token.Claims.(jwt.MapClaims)
	if !ok {
		return nil, false
	}

	// For sub-user tokens, check token_version against stored version (password rotation invalidation)
	if subUser, _ := claims["sub_user"].(string); subUser != "" {
		tokenVersionFloat, hasVersion := claims["token_version"].(float64)
		tokenVersion := int(tokenVersionFloat)
		foundSubUser := false
		for i := range config.AppConfig.SubUsers {
			if config.AppConfig.SubUsers[i].Username == subUser {
				foundSubUser = true
				stored := config.AppConfig.SubUsers[i].TokenVersion
				// If stored version > 0, require token_version to match exactly.
				// This also rejects legacy tokens that lack token_version entirely.
				if stored > 0 && (!hasVersion || tokenVersion != stored) {
					return nil, false
				}
				break
			}
		}
		if !foundSubUser {
			return nil, false
		}
	}

	return claims, ok
}

func claimsFromRequest(r *http.Request) (jwt.MapClaims, bool) {
	return claimsFromToken(tokenFromRequest(r))
}

func isSubUserRequest(r *http.Request) bool {
	if ctx, ok := authContextFromRequest(r); ok {
		return ctx.Type == authTypeSubUser
	}
	claims, ok := claimsFromRequest(r)
	if !ok {
		return false
	}
	_, ok = claims["sub_user"]
	return ok
}

func isAuthenticatedRequest(r *http.Request) bool {
	return isValidToken(tokenFromRequest(r))
}

// HandleLogin processes login requests
func HandleLogin(w http.ResponseWriter, r *http.Request) {
	if r.Method != http.MethodPost {
		jsonResponse(w, http.StatusMethodNotAllowed, APIResponse{Success: false, Message: "Method not allowed"})
		return
	}

	var req LoginRequest
	if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
		jsonResponse(w, http.StatusBadRequest, APIResponse{Success: false, Message: "Invalid request body"})
		return
	}

	ip := clientIP(r)
	ua := r.Header.Get("User-Agent")

	if req.Username != config.AppConfig.AdminUser {
		RecordLoginLog(req.Username, ip, ua, false)
		jsonResponse(w, http.StatusUnauthorized, APIResponse{Success: false, Message: "Invalid credentials"})
		return
	}

	if err := bcrypt.CompareHashAndPassword([]byte(config.AppConfig.AdminPassHash), []byte(req.Password)); err != nil {
		RecordLoginLog(req.Username, ip, ua, false)
		jsonResponse(w, http.StatusUnauthorized, APIResponse{Success: false, Message: "Invalid credentials"})
		return
	}

	RecordLoginLog(req.Username, ip, ua, true)

	// Generate JWT token
	token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{
		"username": req.Username,
		"exp":      time.Now().Add(24 * time.Hour).Unix(),
		"iat":      time.Now().Unix(),
	})

	tokenString, err := token.SignedString([]byte(config.AppConfig.JWTSecret))
	if err != nil {
		jsonResponse(w, http.StatusInternalServerError, APIResponse{Success: false, Message: "Failed to generate token"})
		return
	}

	jsonResponse(w, http.StatusOK, APIResponse{
		Success: true,
		Data: LoginResponse{
			Token:    tokenString,
			Username: req.Username,
		},
	})
}

// HandleChangePassword processes password change requests
func HandleChangePassword(w http.ResponseWriter, r *http.Request) {
	if r.Method != http.MethodPost {
		jsonResponse(w, http.StatusMethodNotAllowed, APIResponse{Success: false, Message: "Method not allowed"})
		return
	}

	var req struct {
		OldPassword string `json:"old_password"`
		NewPassword string `json:"new_password"`
	}
	if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
		jsonResponse(w, http.StatusBadRequest, APIResponse{Success: false, Message: "Invalid request body"})
		return
	}

	if len(req.NewPassword) < 8 {
		jsonResponse(w, http.StatusBadRequest, APIResponse{Success: false, Message: "New password must be at least 8 characters"})
		return
	}

	if err := bcrypt.CompareHashAndPassword([]byte(config.AppConfig.AdminPassHash), []byte(req.OldPassword)); err != nil {
		jsonResponse(w, http.StatusUnauthorized, APIResponse{Success: false, Message: "Current password is incorrect"})
		return
	}

	hash, err := bcrypt.GenerateFromPassword([]byte(req.NewPassword), bcrypt.DefaultCost)
	if err != nil {
		jsonResponse(w, http.StatusInternalServerError, APIResponse{Success: false, Message: "Failed to hash password"})
		return
	}

	config.AppConfig.AdminPassHash = string(hash)
	if err := config.SaveConfig(); err != nil {
		jsonResponse(w, http.StatusInternalServerError, APIResponse{Success: false, Message: "Failed to save configuration"})
		return
	}

	jsonResponse(w, http.StatusOK, APIResponse{Success: true, Message: "Password changed successfully"})
}

// HandleCheckAuth checks if the user is authenticated
func HandleCheckAuth(w http.ResponseWriter, r *http.Request) {
	jsonResponse(w, http.StatusOK, APIResponse{Success: true, Message: "Authenticated"})
}

// AuthMiddleware extracts JWT from cookies or Authorization header
func AuthMiddleware(next http.HandlerFunc) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		tokenString := tokenFromRequest(r)
		if claims, ok := claimsFromToken(tokenString); ok {
			if subUser, _ := claims["sub_user"].(string); subUser != "" {
				auth := AuthContext{Type: authTypeSubUser, Username: subUser, Actor: "user:" + subUser}
				if values, ok := claims["container_uuids"].([]interface{}); ok {
					for _, value := range values {
						if uuid, ok := value.(string); ok {
							auth.ContainerUUIDs = append(auth.ContainerUUIDs, uuid)
						}
					}
				}
				next(w, withAuthContext(r, auth))
				return
			}
			username, _ := claims["username"].(string)
			if username == "" {
				username = config.AppConfig.AdminUser
			}
			next(w, withAuthContext(r, AuthContext{Type: authTypeAdmin, Username: username, Actor: username}))
			return
		}

		if key, ok := validateApiKeyRequest(r); ok {
			next(w, withAuthContext(r, authContextFromAPIKey(key)))
			return
		}

		jsonResponse(w, http.StatusUnauthorized, APIResponse{Success: false, Message: "Authentication required"})
	}
}

// AdminMiddleware requires a valid administrator token and rejects sub-user tokens.
func AdminMiddleware(next http.HandlerFunc) http.HandlerFunc {
	return AuthMiddleware(func(w http.ResponseWriter, r *http.Request) {
		ctx, _ := authContextFromRequest(r)
		if ctx.Type == authTypeSubUser {
			jsonResponse(w, http.StatusForbidden, APIResponse{Success: false, Message: "Administrator permission required"})
			return
		}
		if ctx.Type == authTypeAPIKey && !scopeAllowed(ctx.Scopes, "admin:access") {
			jsonResponse(w, http.StatusForbidden, APIResponse{Success: false, Message: "Administrator permission required"})
			return
		}
		next(w, r)
	})
}
