package server

import (
	"context"
	"crypto/rand"
	"encoding/hex"
	"encoding/json"
	"fmt"
	"io/fs"
	"net/http"
	"time"

	"git.ma-al.com/goc_marek/zfs/internal/server/templates/pages"
)

// Admin authentication and management handlers

// generateToken generates a secure random token
func generateToken() (string, error) {
	bytes := make([]byte, 32)
	if _, err := rand.Read(bytes); err != nil {
		return "", err
	}
	return hex.EncodeToString(bytes), nil
}

// AdminLoginRequest represents a login request
type AdminLoginRequest struct {
	Username string `json:"username"`
	Password string `json:"password"`
}

// AdminLoginResponse represents a login response
type AdminLoginResponse struct {
	Success bool   `json:"success"`
	Token   string `json:"token,omitempty"`
	Message string `json:"message,omitempty"`
}

// handleAdminLogin handles admin login
func (s *Server) handleAdminLogin(w http.ResponseWriter, r *http.Request) {
	if r.Method != http.MethodPost {
		http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
		return
	}

	var req AdminLoginRequest
	if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
		http.Error(w, "Invalid request body", http.StatusBadRequest)
		return
	}

	admin, err := s.db.GetAdminByUsername(req.Username)
	if err != nil {
		http.Error(w, "Database error", http.StatusInternalServerError)
		return
	}

	if admin == nil {
		json.NewEncoder(w).Encode(AdminLoginResponse{
			Success: false,
			Message: "Invalid credentials",
		})
		return
	}

	// Verify password
	if admin.PasswordHash != hashAPIKey(req.Password) {
		json.NewEncoder(w).Encode(AdminLoginResponse{
			Success: false,
			Message: "Invalid credentials",
		})
		return
	}

	// Generate session token
	token, err := generateToken()
	if err != nil {
		http.Error(w, "Failed to generate token", http.StatusInternalServerError)
		return
	}

	// Create session (valid for 24 hours)
	expiresAt := time.Now().Add(24 * time.Hour)
	if err := s.db.CreateSession(admin.ID, token, expiresAt); err != nil {
		http.Error(w, "Failed to create session", http.StatusInternalServerError)
		return
	}

	// Set cookie
	http.SetCookie(w, &http.Cookie{
		Name:     "admin_token",
		Value:    token,
		Path:     "/",
		Expires:  expiresAt,
		HttpOnly: true,
		SameSite: http.SameSiteStrictMode,
	})

	w.Header().Set("Content-Type", "application/json")
	json.NewEncoder(w).Encode(AdminLoginResponse{
		Success: true,
		Token:   token,
		Message: "Login successful",
	})
}

// handleAdminLogout handles admin logout
func (s *Server) handleAdminLogout(w http.ResponseWriter, r *http.Request) {
	cookie, err := r.Cookie("admin_token")
	if err == nil {
		s.db.DeleteSession(cookie.Value)
	}

	http.SetCookie(w, &http.Cookie{
		Name:     "admin_token",
		Value:    "",
		Path:     "/",
		MaxAge:   -1,
		HttpOnly: true,
	})

	w.Header().Set("Content-Type", "application/json")
	json.NewEncoder(w).Encode(map[string]interface{}{
		"success": true,
		"message": "Logged out successfully",
	})
}

// authenticateAdmin checks if the request has a valid admin session
func (s *Server) authenticateAdmin(r *http.Request) (*Admin, error) {
	cookie, err := r.Cookie("admin_token")
	if err != nil {
		return nil, err
	}

	session, err := s.db.GetSessionByToken(cookie.Value)
	if err != nil {
		return nil, err
	}

	if session == nil {
		return nil, nil
	}

	if session.ExpiresAt.Before(time.Now()) {
		s.db.DeleteSession(cookie.Value)
		return nil, nil
	}

	admin, err := s.db.GetAdminByID(session.AdminID)
	if err != nil {
		return nil, err
	}

	return admin, nil
}

// handleAdminCheck checks if admin is authenticated
func (s *Server) handleAdminCheck(w http.ResponseWriter, r *http.Request) {
	admin, err := s.authenticateAdmin(r)
	if err != nil || admin == nil {
		w.Header().Set("Content-Type", "application/json")
		json.NewEncoder(w).Encode(map[string]interface{}{
			"authenticated": false,
		})
		return
	}

	w.Header().Set("Content-Type", "application/json")
	json.NewEncoder(w).Encode(map[string]interface{}{
		"authenticated": true,
		"username":      admin.Username,
		"role":          admin.Role,
	})
}

// Client management handlers

// handleAdminGetClients returns all clients
func (s *Server) handleAdminGetClients(w http.ResponseWriter, r *http.Request) {
	admin, err := s.authenticateAdmin(r)
	if err != nil || admin == nil {
		http.Error(w, "Unauthorized", http.StatusUnauthorized)
		return
	}

	clients, err := s.db.GetAllClients()
	if err != nil {
		http.Error(w, "Database error", http.StatusInternalServerError)
		return
	}

	// Add usage info for each client
	type ClientWithUsage struct {
		*ClientConfig
		CurrentUsage  int64 `json:"current_usage"`
		SnapshotCount int   `json:"snapshot_count"`
	}

	var result []ClientWithUsage
	for _, c := range clients {
		usage, _ := s.db.GetClientUsage(c.ClientID)
		snapshots, _ := s.db.GetSnapshotsByClient(c.ClientID)
		result = append(result, ClientWithUsage{
			ClientConfig:  c,
			CurrentUsage:  usage,
			SnapshotCount: len(snapshots),
		})
	}

	w.Header().Set("Content-Type", "application/json")
	json.NewEncoder(w).Encode(result)
}

// handleAdminGetClient returns a specific client
func (s *Server) handleAdminGetClient(w http.ResponseWriter, r *http.Request) {
	admin, err := s.authenticateAdmin(r)
	if err != nil || admin == nil {
		http.Error(w, "Unauthorized", http.StatusUnauthorized)
		return
	}

	clientID := r.URL.Query().Get("client_id")
	if clientID == "" {
		http.Error(w, "client_id required", http.StatusBadRequest)
		return
	}

	client, err := s.db.GetClient(clientID)
	if err != nil {
		http.Error(w, "Database error", http.StatusInternalServerError)
		return
	}

	if client == nil {
		http.Error(w, "Client not found", http.StatusNotFound)
		return
	}

	usage, _ := s.db.GetClientUsage(clientID)
	snapshots, _ := s.db.GetSnapshotsByClient(clientID)

	w.Header().Set("Content-Type", "application/json")
	json.NewEncoder(w).Encode(map[string]interface{}{
		"client":         client,
		"current_usage":  usage,
		"snapshot_count": len(snapshots),
	})
}

// handleAdminCreateClient creates a new client
func (s *Server) handleAdminCreateClient(w http.ResponseWriter, r *http.Request) {
	admin, err := s.authenticateAdmin(r)
	if err != nil || admin == nil {
		http.Error(w, "Unauthorized", http.StatusUnauthorized)
		return
	}

	if r.Method != http.MethodPost {
		http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
		return
	}

	var req struct {
		ClientID       string          `json:"client_id"`
		APIKey         string          `json:"api_key"`
		MaxSizeBytes   int64           `json:"max_size_bytes"`
		Dataset        string          `json:"dataset"`
		StorageType    string          `json:"storage_type"`
		Enabled        bool            `json:"enabled"`
		RotationPolicy *RotationPolicy `json:"rotation_policy"`
	}

	if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
		http.Error(w, "Invalid request body", http.StatusBadRequest)
		return
	}

	if req.ClientID == "" || req.APIKey == "" {
		http.Error(w, "client_id and api_key required", http.StatusBadRequest)
		return
	}

	if req.StorageType == "" {
		req.StorageType = "s3"
	}

	client := &ClientConfig{
		ClientID:       req.ClientID,
		APIKey:         hashAPIKey(req.APIKey),
		MaxSizeBytes:   req.MaxSizeBytes,
		Dataset:        req.Dataset,
		StorageType:    req.StorageType,
		Enabled:        req.Enabled,
		RotationPolicy: req.RotationPolicy,
	}

	if err := s.db.SaveClient(client); err != nil {
		http.Error(w, "Failed to create client", http.StatusInternalServerError)
		return
	}

	w.Header().Set("Content-Type", "application/json")
	json.NewEncoder(w).Encode(map[string]interface{}{
		"success": true,
		"message": "Client created successfully",
	})
}

// handleAdminUpdateClient updates an existing client
func (s *Server) handleAdminUpdateClient(w http.ResponseWriter, r *http.Request) {
	admin, err := s.authenticateAdmin(r)
	if err != nil || admin == nil {
		http.Error(w, "Unauthorized", http.StatusUnauthorized)
		return
	}

	if r.Method != http.MethodPut && r.Method != http.MethodPost {
		http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
		return
	}

	var req struct {
		ClientID       string          `json:"client_id"`
		APIKey         string          `json:"api_key"`
		MaxSizeBytes   int64           `json:"max_size_bytes"`
		Dataset        string          `json:"dataset"`
		StorageType    string          `json:"storage_type"`
		Enabled        bool            `json:"enabled"`
		RotationPolicy *RotationPolicy `json:"rotation_policy"`
	}

	if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
		http.Error(w, "Invalid request body", http.StatusBadRequest)
		return
	}

	if req.ClientID == "" {
		http.Error(w, "client_id required", http.StatusBadRequest)
		return
	}

	// Get existing client
	existing, err := s.db.GetClient(req.ClientID)
	if err != nil {
		http.Error(w, "Database error", http.StatusInternalServerError)
		return
	}

	if existing == nil {
		http.Error(w, "Client not found", http.StatusNotFound)
		return
	}

	// Update fields
	if req.APIKey != "" {
		existing.APIKey = hashAPIKey(req.APIKey)
	}
	if req.MaxSizeBytes > 0 {
		existing.MaxSizeBytes = req.MaxSizeBytes
	}
	if req.Dataset != "" {
		existing.Dataset = req.Dataset
	}
	if req.StorageType != "" {
		existing.StorageType = req.StorageType
	}
	existing.Enabled = req.Enabled
	if req.RotationPolicy != nil {
		existing.RotationPolicy = req.RotationPolicy
	}

	if err := s.db.SaveClient(existing); err != nil {
		http.Error(w, "Failed to update client", http.StatusInternalServerError)
		return
	}

	w.Header().Set("Content-Type", "application/json")
	json.NewEncoder(w).Encode(map[string]interface{}{
		"success": true,
		"message": "Client updated successfully",
	})
}

// handleAdminDeleteClient deletes a client
func (s *Server) handleAdminDeleteClient(w http.ResponseWriter, r *http.Request) {
	admin, err := s.authenticateAdmin(r)
	if err != nil || admin == nil {
		http.Error(w, "Unauthorized", http.StatusUnauthorized)
		return
	}

	if r.Method != http.MethodDelete && r.Method != http.MethodPost {
		http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
		return
	}

	clientID := r.URL.Query().Get("client_id")
	if clientID == "" {
		http.Error(w, "client_id required", http.StatusBadRequest)
		return
	}

	// Delete snapshots first (handled by foreign key cascade)
	// Get snapshots to delete from storage
	snapshots, _ := s.db.GetSnapshotsByClient(clientID)
	for _, snap := range snapshots {
		if snap.StorageType == "s3" && s.s3Backend != nil {
			s.s3Backend.Delete(context.Background(), snap.StorageKey)
		}
	}

	if err := s.db.DeleteClient(clientID); err != nil {
		http.Error(w, "Failed to delete client", http.StatusInternalServerError)
		return
	}

	w.Header().Set("Content-Type", "application/json")
	json.NewEncoder(w).Encode(map[string]interface{}{
		"success": true,
		"message": "Client deleted successfully",
	})
}

// handleAdminGetSnapshots returns all snapshots for a client
func (s *Server) handleAdminGetSnapshots(w http.ResponseWriter, r *http.Request) {
	admin, err := s.authenticateAdmin(r)
	if err != nil || admin == nil {
		http.Error(w, "Unauthorized", http.StatusUnauthorized)
		return
	}

	clientID := r.URL.Query().Get("client_id")
	var snapshots []*SnapshotMetadata

	if clientID != "" {
		snapshots, err = s.db.GetSnapshotsByClient(clientID)
	} else {
		snapshots, err = s.db.GetAllSnapshots()
	}

	if err != nil {
		http.Error(w, "Database error", http.StatusInternalServerError)
		return
	}

	w.Header().Set("Content-Type", "application/json")
	json.NewEncoder(w).Encode(snapshots)
}

// handleAdminDeleteSnapshot deletes a specific snapshot
func (s *Server) handleAdminDeleteSnapshot(w http.ResponseWriter, r *http.Request) {
	admin, err := s.authenticateAdmin(r)
	if err != nil || admin == nil {
		http.Error(w, "Unauthorized", http.StatusUnauthorized)
		return
	}

	if r.Method != http.MethodDelete && r.Method != http.MethodPost {
		http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
		return
	}

	clientID := r.URL.Query().Get("client_id")
	snapshotID := r.URL.Query().Get("snapshot_id")

	if clientID == "" || snapshotID == "" {
		http.Error(w, "client_id and snapshot_id required", http.StatusBadRequest)
		return
	}

	// Get snapshot to delete from storage
	snap, err := s.db.GetSnapshotByID(clientID, snapshotID)
	if err != nil {
		http.Error(w, "Database error", http.StatusInternalServerError)
		return
	}

	if snap != nil {
		if snap.StorageType == "s3" && s.s3Backend != nil {
			s.s3Backend.Delete(context.Background(), snap.StorageKey)
		}
	}

	if err := s.db.DeleteSnapshot(clientID, snapshotID); err != nil {
		http.Error(w, "Failed to delete snapshot", http.StatusInternalServerError)
		return
	}

	w.Header().Set("Content-Type", "application/json")
	json.NewEncoder(w).Encode(map[string]interface{}{
		"success": true,
		"message": "Snapshot deleted successfully",
	})
}

// handleAdminGetStats returns server statistics
func (s *Server) handleAdminGetStats(w http.ResponseWriter, r *http.Request) {
	admin, err := s.authenticateAdmin(r)
	if err != nil || admin == nil {
		http.Error(w, "Unauthorized", http.StatusUnauthorized)
		return
	}

	clients, _ := s.db.GetAllClients()
	totalSnapshots, _ := s.db.GetTotalSnapshotCount()
	totalStorage, _ := s.db.GetTotalStorageUsed()

	w.Header().Set("Content-Type", "application/json")
	json.NewEncoder(w).Encode(map[string]interface{}{
		"client_count":     len(clients),
		"total_snapshots":  totalSnapshots,
		"total_storage":    totalStorage,
		"total_storage_gb": float64(totalStorage) / (1024 * 1024 * 1024),
	})
}

// Admin management handlers

// handleAdminGetAdmins returns all admins
func (s *Server) handleAdminGetAdmins(w http.ResponseWriter, r *http.Request) {
	admin, err := s.authenticateAdmin(r)
	if err != nil || admin == nil {
		http.Error(w, "Unauthorized", http.StatusUnauthorized)
		return
	}

	admins, err := s.db.GetAllAdmins()
	if err != nil {
		http.Error(w, "Database error", http.StatusInternalServerError)
		return
	}

	// Remove password hashes from response
	type AdminResponse struct {
		ID        int       `json:"id"`
		Username  string    `json:"username"`
		Role      string    `json:"role"`
		CreatedAt time.Time `json:"created_at"`
		UpdatedAt time.Time `json:"updated_at"`
	}

	var result []AdminResponse
	for _, a := range admins {
		result = append(result, AdminResponse{
			ID:        a.ID,
			Username:  a.Username,
			Role:      a.Role,
			CreatedAt: a.CreatedAt,
			UpdatedAt: a.UpdatedAt,
		})
	}

	w.Header().Set("Content-Type", "application/json")
	json.NewEncoder(w).Encode(result)
}

// handleAdminCreateAdmin creates a new admin
func (s *Server) handleAdminCreateAdmin(w http.ResponseWriter, r *http.Request) {
	admin, err := s.authenticateAdmin(r)
	if err != nil || admin == nil {
		http.Error(w, "Unauthorized", http.StatusUnauthorized)
		return
	}

	if r.Method != http.MethodPost {
		http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
		return
	}

	var req struct {
		Username string `json:"username"`
		Password string `json:"password"`
		Role     string `json:"role"`
	}

	if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
		http.Error(w, "Invalid request body", http.StatusBadRequest)
		return
	}

	if req.Username == "" || req.Password == "" {
		http.Error(w, "username and password required", http.StatusBadRequest)
		return
	}

	if req.Role == "" {
		req.Role = "admin"
	}

	if err := s.db.CreateAdmin(req.Username, hashAPIKey(req.Password), req.Role); err != nil {
		http.Error(w, "Failed to create admin (username may already exist)", http.StatusInternalServerError)
		return
	}

	w.Header().Set("Content-Type", "application/json")
	json.NewEncoder(w).Encode(map[string]interface{}{
		"success": true,
		"message": "Admin created successfully",
	})
}

// handleAdminDeleteAdmin deletes an admin
func (s *Server) handleAdminDeleteAdmin(w http.ResponseWriter, r *http.Request) {
	admin, err := s.authenticateAdmin(r)
	if err != nil || admin == nil {
		http.Error(w, "Unauthorized", http.StatusUnauthorized)
		return
	}

	if r.Method != http.MethodDelete && r.Method != http.MethodPost {
		http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
		return
	}

	id := r.URL.Query().Get("id")
	if id == "" {
		http.Error(w, "id required", http.StatusBadRequest)
		return
	}

	// Prevent deleting yourself
	var targetAdminID int
	fmt.Sscanf(id, "%d", &targetAdminID)
	if admin != nil && targetAdminID == admin.ID {
		http.Error(w, "Cannot delete yourself", http.StatusBadRequest)
		return
	}

	if err := s.db.DeleteAdmin(targetAdminID); err != nil {
		http.Error(w, "Failed to delete admin", http.StatusInternalServerError)
		return
	}

	w.Header().Set("Content-Type", "application/json")
	json.NewEncoder(w).Encode(map[string]interface{}{
		"success": true,
		"message": "Admin deleted successfully",
	})
}

// handleAdminChangePassword handles admin password change
func (s *Server) handleAdminChangePassword(w http.ResponseWriter, r *http.Request) {
	admin, err := s.authenticateAdmin(r)
	if err != nil || admin == nil {
		http.Error(w, "Unauthorized", http.StatusUnauthorized)
		return
	}

	if r.Method != http.MethodPost {
		http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
		return
	}

	var req struct {
		ID       int    `json:"id"`
		Password string `json:"password"`
	}

	if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
		http.Error(w, "Invalid request body", http.StatusBadRequest)
		return
	}

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

	if err := s.db.UpdateAdminPassword(req.ID, hashAPIKey(req.Password)); err != nil {
		http.Error(w, "Failed to update password", http.StatusInternalServerError)
		return
	}

	w.Header().Set("Content-Type", "application/json")
	json.NewEncoder(w).Encode(map[string]interface{}{
		"success": true,
		"message": "Password changed successfully",
	})
}

// handleAdminUI serves the admin panel UI
func (s *Server) handleAdminUI(w http.ResponseWriter, r *http.Request) {
	// Check if admin is authenticated
	admin, _ := s.authenticateAdmin(r)

	if admin != nil {
		// Serve the admin panel using templ
		w.Header().Set("Content-Type", "text/html; charset=utf-8")
		pages.AdminPage(admin.Username).Render(r.Context(), w)
	} else {
		// Serve the login page using templ
		w.Header().Set("Content-Type", "text/html; charset=utf-8")
		pages.LoginPage().Render(r.Context(), w)
	}
}

// handleAdminStatic serves static files for the admin panel
func (s *Server) handleAdminStatic(w http.ResponseWriter, r *http.Request) {
	// Extract the file path from the URL
	// URL format: /admin/static/<filename>
	path := r.URL.Path[len("/admin/static/"):]

	// Get the embedded filesystem
	staticFS, err := GetStaticFS()
	if err != nil {
		http.Error(w, "Failed to load static files", http.StatusInternalServerError)
		return
	}

	// Serve the file
	content, err := fs.ReadFile(staticFS, path)
	if err != nil {
		http.NotFound(w, r)
		return
	}

	// Set content type based on file extension
	switch {
	case len(path) >= 3 && path[len(path)-3:] == ".js":
		w.Header().Set("Content-Type", "application/javascript; charset=utf-8")
	case len(path) >= 4 && path[len(path)-4:] == ".css":
		w.Header().Set("Content-Type", "text/css; charset=utf-8")
	default:
		w.Header().Set("Content-Type", "application/octet-stream")
	}

	w.Write(content)
}

// handleAdminResetClientPassword resets a client's API key to a new random value
func (s *Server) handleAdminResetClientPassword(w http.ResponseWriter, r *http.Request) {
	admin, err := s.authenticateAdmin(r)
	if err != nil || admin == nil {
		http.Error(w, "Unauthorized", http.StatusUnauthorized)
		return
	}

	if r.Method != http.MethodPost {
		http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
		return
	}

	clientID := r.URL.Query().Get("client_id")
	if clientID == "" {
		http.Error(w, "client_id required", http.StatusBadRequest)
		return
	}

	// Get existing client
	client, err := s.db.GetClient(clientID)
	if err != nil {
		http.Error(w, "Database error", http.StatusInternalServerError)
		return
	}

	if client == nil {
		http.Error(w, "Client not found", http.StatusNotFound)
		return
	}

	// Generate new random API key
	newAPIKey, err := generateToken()
	if err != nil {
		http.Error(w, "Failed to generate new API key", http.StatusInternalServerError)
		return
	}

	// Update client with new API key
	client.APIKey = hashAPIKey(newAPIKey)
	if err := s.db.SaveClient(client); err != nil {
		http.Error(w, "Failed to update client", http.StatusInternalServerError)
		return
	}

	w.Header().Set("Content-Type", "application/json")
	json.NewEncoder(w).Encode(map[string]interface{}{
		"success": true,
		"message": "Client password reset successfully",
		"api_key": newAPIKey, // Return the new key (only shown once!)
	})
}

// handleClientChangePassword allows a client to change its own API key
func (s *Server) handleClientChangePassword(w http.ResponseWriter, r *http.Request) {
	if r.Method != http.MethodPost {
		http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
		return
	}

	var req struct {
		ClientID   string `json:"client_id"`
		CurrentKey string `json:"current_key"`
		NewKey     string `json:"new_key"`
	}

	if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
		http.Error(w, "Invalid request body", http.StatusBadRequest)
		return
	}

	if req.ClientID == "" || req.CurrentKey == "" || req.NewKey == "" {
		http.Error(w, "client_id, current_key, and new_key required", http.StatusBadRequest)
		return
	}

	// Authenticate with current key
	if !s.authenticate(req.ClientID, req.CurrentKey) {
		http.Error(w, "Invalid current API key", http.StatusUnauthorized)
		return
	}

	// Get client
	client, err := s.db.GetClient(req.ClientID)
	if err != nil || client == nil {
		http.Error(w, "Client not found", http.StatusNotFound)
		return
	}

	// Update with new key
	client.APIKey = hashAPIKey(req.NewKey)
	if err := s.db.SaveClient(client); err != nil {
		http.Error(w, "Failed to update password", http.StatusInternalServerError)
		return
	}

	w.Header().Set("Content-Type", "application/json")
	json.NewEncoder(w).Encode(map[string]interface{}{
		"success": true,
		"message": "Password changed successfully",
	})
}
