package handlers

import (
	"context"
	"crypto/rand"
	"encoding/base64"
	"encoding/json"
	"net/http"
	"time"

	"golang.org/x/crypto/bcrypt"

	"lookbook/internal/data/admin"
	"lookbook/internal/data/session"
)

const sessionDuration = 30 * 24 * time.Hour // 30 days

type loginRequest struct {
	Password string `json:"password"`
}

type loginResponse struct {
	FirstTime bool   `json:"firstTime,omitempty"`
	Error     string `json:"error,omitempty"`
}

type changePasswordRequest struct {
	CurrentPassword string `json:"currentPassword"`
	NewPassword     string `json:"newPassword"`
}

// HandleLogin handles POST /api/auth/login
// If no password is set, it sets the password. Otherwise, it verifies the password.
func HandleLogin(rc *RequestContext, w http.ResponseWriter, r *http.Request) error {
	var req loginRequest
	if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
		return writeJSON(w, http.StatusBadRequest, loginResponse{Error: "invalid request"})
	}

	if req.Password == "" {
		return writeJSON(w, http.StatusBadRequest, loginResponse{Error: "password required"})
	}

	ctx, cancel := context.WithTimeout(r.Context(), 5*time.Second)
	defer cancel()

	adm, err := admin.QGet(ctx, rc.DB)
	if err != nil {
		return err
	}

	firstTime := adm.PasswordHash == nil

	if firstTime {
		// First login: set the password
		hash, err := bcrypt.GenerateFromPassword([]byte(req.Password), bcrypt.DefaultCost)
		if err != nil {
			return err
		}
		if err := admin.QSetPassword(ctx, rc.DB, hash); err != nil {
			return err
		}
	} else {
		// Verify password
		if err := bcrypt.CompareHashAndPassword(adm.PasswordHash, []byte(req.Password)); err != nil {
			return writeJSON(w, http.StatusUnauthorized, loginResponse{Error: "invalid password"})
		}
	}

	// Create session
	sessionID, err := generateSessionID()
	if err != nil {
		return err
	}

	expiresAt := time.Now().Add(sessionDuration)
	if _, err := session.QCreate(ctx, rc.DB, sessionID, expiresAt); err != nil {
		return err
	}

	// Set cookie
	http.SetCookie(w, &http.Cookie{
		Name:     "session_id",
		Value:    sessionID,
		Path:     "/",
		Expires:  expiresAt,
		HttpOnly: true,
		SameSite: http.SameSiteLaxMode,
		Secure:   r.TLS != nil,
	})

	return writeJSON(w, http.StatusOK, loginResponse{FirstTime: firstTime})
}

// HandleLogout handles POST /api/auth/logout
func HandleLogout(rc *RequestContext, w http.ResponseWriter, r *http.Request) error {
	cookie, err := r.Cookie("session_id")
	if err == nil {
		ctx, cancel := context.WithTimeout(r.Context(), 5*time.Second)
		defer cancel()
		session.QDelete(ctx, rc.DB, cookie.Value)
	}

	// Clear cookie
	http.SetCookie(w, &http.Cookie{
		Name:     "session_id",
		Value:    "",
		Path:     "/",
		MaxAge:   -1,
		HttpOnly: true,
		SameSite: http.SameSiteLaxMode,
	})

	return writeJSON(w, http.StatusOK, map[string]string{"status": "ok"})
}

// HandleAuthStatus handles GET /api/auth/status
func HandleAuthStatus(rc *RequestContext, w http.ResponseWriter, r *http.Request) error {
	ctx, cancel := context.WithTimeout(r.Context(), 5*time.Second)
	defer cancel()

	hasPassword, err := admin.QHasPassword(ctx, rc.DB)
	if err != nil {
		return err
	}

	return writeJSON(w, http.StatusOK, map[string]any{
		"isAdmin":     rc.IsAdmin,
		"passwordSet": hasPassword,
	})
}

// HandleChangePassword handles POST /api/auth/password
func HandleChangePassword(rc *RequestContext, w http.ResponseWriter, r *http.Request) error {
	if !rc.RequireAdmin(w) {
		return nil
	}

	var req changePasswordRequest
	if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
		return writeJSON(w, http.StatusBadRequest, map[string]string{"error": "invalid request"})
	}

	if req.CurrentPassword == "" || req.NewPassword == "" {
		return writeJSON(w, http.StatusBadRequest, map[string]string{"error": "both passwords required"})
	}

	ctx, cancel := context.WithTimeout(r.Context(), 5*time.Second)
	defer cancel()

	// Get current password hash
	adm, err := admin.QGet(ctx, rc.DB)
	if err != nil {
		return err
	}

	// Verify current password
	if err := bcrypt.CompareHashAndPassword(adm.PasswordHash, []byte(req.CurrentPassword)); err != nil {
		return writeJSON(w, http.StatusUnauthorized, map[string]string{"error": "invalid current password"})
	}

	// Hash new password
	hash, err := bcrypt.GenerateFromPassword([]byte(req.NewPassword), bcrypt.DefaultCost)
	if err != nil {
		return err
	}

	// Update password
	if err := admin.QSetPassword(ctx, rc.DB, hash); err != nil {
		return err
	}

	// Invalidate all other sessions (keep current session)
	cookie, _ := r.Cookie("session_id")
	if cookie != nil {
		session.QDeleteAllExcept(ctx, rc.DB, cookie.Value)
	}

	return writeJSON(w, http.StatusOK, map[string]string{"status": "ok"})
}

func generateSessionID() (string, error) {
	b := make([]byte, 32)
	if _, err := rand.Read(b); err != nil {
		return "", err
	}
	return base64.URLEncoding.EncodeToString(b), nil
}

func writeJSON(w http.ResponseWriter, status int, v any) error {
	w.Header().Set("Content-Type", "application/json")
	w.WriteHeader(status)
	return json.NewEncoder(w).Encode(v)
}
