package handlers

import (
	"bookhoard/internal/database"
	"context"
	"errors"
	"fmt"
	"net/http"
	"strconv"
	"time"

	"github.com/jackc/pgx/v5"
	"github.com/jackc/pgx/v5/pgtype"
	"github.com/labstack/echo/v5"
)

type SystemSettingsHandler struct {
	db       *database.Queries
	settings *database.SettingsRegistry
}

func NewSystemSettingsHandler(db *database.Queries) *SystemSettingsHandler {
	return &SystemSettingsHandler{
		db: db,
	}
}

// SetSettings wires the tunable settings registry. Required for the unified
// /api/system/settings endpoints and for cache invalidation after writes.
func (h *SystemSettingsHandler) SetSettings(s *database.SettingsRegistry) {
	h.settings = s
}

// reload refreshes the in-memory cache after a write.
func (h *SystemSettingsHandler) reload(c *echo.Context) {
	if h.settings != nil {
		h.settings.Reload(c.Request().Context())
	}
}

// ---- Unified /api/system/settings endpoints ----

// GetSettings handles GET /api/system/settings.
func (h *SystemSettingsHandler) GetSettings(c *echo.Context) error {
	if h.settings == nil {
		return c.JSON(http.StatusServiceUnavailable, map[string]string{"error": "settings registry not initialized"})
	}
	return c.JSON(http.StatusOK, h.settings.All())
}

// UpdateSettingRequest is the body for PUT /api/system/settings.
type UpdateSettingRequest struct {
	Key   string `json:"key" form:"key"`
	Value string `json:"value" form:"value"`
}

// UpdateSettingResponse mirrors a settings entry plus a reload hint.
type UpdateSettingResponse struct {
	database.SettingEntry
	ReloadRequired bool   `json:"reload_required"`
	Message        string `json:"message,omitempty"`
}

// UpdateSetting handles PUT /api/system/settings.
func (h *SystemSettingsHandler) UpdateSetting(c *echo.Context) error {
	if h.settings == nil {
		return c.JSON(http.StatusServiceUnavailable, map[string]string{"error": "settings registry not initialized"})
	}
	var req UpdateSettingRequest
	if err := c.Bind(&req); err != nil {
		return c.JSON(http.StatusBadRequest, map[string]string{"error": "invalid request"})
	}
	resp, err := h.ApplySetting(c.Request().Context(), req.Key, req.Value)
	if err != nil {
		return c.JSON(http.StatusBadRequest, map[string]string{"error": err.Error()})
	}
	return c.JSON(http.StatusOK, resp)
}

// ApplySetting validates, persists, and reloads a single setting. Shared by the
// JSON API and the HTMX admin endpoint.
func (h *SystemSettingsHandler) ApplySetting(ctx context.Context, key, value string) (UpdateSettingResponse, error) {
	if h.settings == nil {
		return UpdateSettingResponse{}, fmt.Errorf("settings registry not initialized")
	}
	if key == "" {
		return UpdateSettingResponse{}, fmt.Errorf("key is required")
	}
	def, ok := database.LookupDefault(key)
	if !ok {
		return UpdateSettingResponse{}, fmt.Errorf("unknown setting key: %s", key)
	}
	if err := validateSettingValue(def, value); err != nil {
		return UpdateSettingResponse{}, err
	}

	desc := def.Description
	rType := pgtype.Text{}
	if def.Type != "" {
		rType = pgtype.Text{String: def.Type, Valid: true}
	}
	var minP, maxP pgtype.Text
	if def.Min != "" {
		minP = pgtype.Text{String: def.Min, Valid: true}
	}
	if def.Max != "" {
		maxP = pgtype.Text{String: def.Max, Valid: true}
	}
	if _, err := h.db.UpsertSystemSetting(ctx, database.UpsertSystemSettingParams{
		SettingKey:      key,
		SettingValue:    value,
		Description:     pgtype.Text{String: desc, Valid: desc != ""},
		SettingType:     rType,
		MinValue:        minP,
		MaxValue:        maxP,
		RequiresRestart: pgtype.Bool{Bool: def.RequiresRestart, Valid: true},
		Category:        pgtype.Text{String: def.Category, Valid: def.Category != ""},
	}); err != nil {
		return UpdateSettingResponse{}, err
	}

	h.settings.Reload(ctx)

	resp := UpdateSettingResponse{ReloadRequired: def.RequiresRestart}
	for _, e := range h.settings.All() {
		if e.Key == key {
			resp.SettingEntry = e
			break
		}
	}
	if def.RequiresRestart {
		resp.Message = "Saved. Restart the server for this change to take full effect."
	} else {
		resp.Message = "Saved."
	}
	return resp, nil
}

// validateSettingValue checks a candidate value against the setting's type and bounds.
func validateSettingValue(def database.SettingDefault, value string) error {
	switch def.Type {
	case database.SettingTypeInt:
		n, err := strconv.Atoi(value)
		if err != nil {
			return fmt.Errorf("value must be an integer")
		}
		if def.Min != "" {
			if mn, err := strconv.Atoi(def.Min); err == nil && n < mn {
				return fmt.Errorf("value must be >= %s", def.Min)
			}
		}
		if def.Max != "" {
			if mx, err := strconv.Atoi(def.Max); err == nil && n > mx {
				return fmt.Errorf("value must be <= %s", def.Max)
			}
		}
	case database.SettingTypeBool:
		if _, err := strconv.ParseBool(value); err != nil {
			return fmt.Errorf("value must be true or false")
		}
	case database.SettingTypeString:
		if value == "" {
			return fmt.Errorf("value must not be empty")
		}
		if def.Key == "default_timezone" {
			if _, err := time.LoadLocation(value); err != nil {
				return fmt.Errorf("invalid timezone: %v", err)
			}
		}
	}
	return nil
}

// ---- Legacy scan-settings endpoints (retained for backward compatibility) ----

type UpdateScanSettingsRequest struct {
	ScanPollIntervalSeconds int32 `json:"scan_poll_interval_seconds" validate:"required,min=1,max=3600"`
	AutoScanEnabled         bool  `json:"auto_scan_enabled"`
}

type ScanSettingsResponse struct {
	ScanPollIntervalSeconds int32  `json:"scan_poll_interval_seconds"`
	AutoScanEnabled         bool   `json:"auto_scan_enabled"`
	Message                 string `json:"message,omitempty"`
}

type UpdateTimezoneSettingsRequest struct {
	DefaultTimezone string `json:"default_timezone" validate:"required"`
}

func (h *SystemSettingsHandler) UpdateTimezoneSettings(c *echo.Context) error {
	var req UpdateTimezoneSettingsRequest
	if err := c.Bind(&req); err != nil {
		return c.JSON(http.StatusBadRequest, map[string]string{"error": "Invalid request"})
	}
	if _, err := time.LoadLocation(req.DefaultTimezone); err != nil {
		return c.JSON(http.StatusBadRequest, map[string]string{"error": "Invalid timezone"})
	}
	err := h.db.UpdateSystemSetting(c.Request().Context(), database.UpdateSystemSettingParams{
		SettingKey:   "default_timezone",
		SettingValue: req.DefaultTimezone,
	})
	if err != nil {
		return err
	}
	h.reload(c)
	return c.JSON(http.StatusOK, map[string]string{"message": "Timezone updated"})
}

func (h *SystemSettingsHandler) UpdateScanSettings(c *echo.Context) error {
	var req UpdateScanSettingsRequest
	if err := c.Bind(&req); err != nil {
		return c.JSON(http.StatusBadRequest, map[string]string{"error": "invalid request"})
	}
	if err := c.Validate(&req); err != nil {
		return c.JSON(http.StatusBadRequest, map[string]string{"error": err.Error()})
	}

	scanFrequencyValue := strconv.FormatInt(int64(req.ScanPollIntervalSeconds), 10)
	autoScanValue := strconv.FormatBool(req.AutoScanEnabled)

	err := h.db.UpdateSystemSetting(c.Request().Context(), database.UpdateSystemSettingParams{
		SettingKey:   "scan_poll_interval_seconds",
		SettingValue: scanFrequencyValue,
	})
	if err != nil {
		if errors.Is(err, pgx.ErrNoRows) {
			return c.JSON(http.StatusNotFound, map[string]string{"error": "system setting not found"})
		}
		return c.JSON(http.StatusInternalServerError, map[string]string{"error": err.Error()})
	}

	err = h.db.UpdateSystemSetting(c.Request().Context(), database.UpdateSystemSettingParams{
		SettingKey:   "auto_scan_enabled",
		SettingValue: autoScanValue,
	})
	if err != nil {
		if errors.Is(err, pgx.ErrNoRows) {
			return c.JSON(http.StatusNotFound, map[string]string{"error": "system setting not found"})
		}
		return c.JSON(http.StatusInternalServerError, map[string]string{"error": err.Error()})
	}

	h.reload(c)

	return c.JSON(http.StatusOK, ScanSettingsResponse{
		ScanPollIntervalSeconds: req.ScanPollIntervalSeconds,
		AutoScanEnabled:         req.AutoScanEnabled,
		Message:                 "scan settings updated successfully",
	})
}

func (h *SystemSettingsHandler) GetScanSettings(c *echo.Context) error {
	// Prefer the registry (single source of truth after Load).
	if h.settings != nil {
		interval := int32(h.settings.ScanPollInterval().Seconds())
		return c.JSON(http.StatusOK, ScanSettingsResponse{
			ScanPollIntervalSeconds: interval,
			AutoScanEnabled:         h.settings.AutoScanEnabled(),
		})
	}

	scanFrequencySetting, err := h.db.GetSystemSetting(c.Request().Context(), "scan_poll_interval_seconds")
	if err != nil {
		if errors.Is(err, pgx.ErrNoRows) {
			return c.JSON(http.StatusOK, ScanSettingsResponse{
				ScanPollIntervalSeconds: 1800,
				AutoScanEnabled:         true,
			})
		}
		return c.JSON(http.StatusInternalServerError, map[string]string{"error": err.Error()})
	}

	autoScanSetting, err := h.db.GetSystemSetting(c.Request().Context(), "auto_scan_enabled")
	if err != nil {
		if errors.Is(err, pgx.ErrNoRows) {
			return c.JSON(http.StatusOK, ScanSettingsResponse{
				ScanPollIntervalSeconds: 1800,
				AutoScanEnabled:         true,
			})
		}
		return c.JSON(http.StatusInternalServerError, map[string]string{"error": err.Error()})
	}

	scanFrequency, err := strconv.ParseInt(scanFrequencySetting, 10, 32)
	if err != nil {
		return c.JSON(http.StatusInternalServerError, map[string]string{"error": "invalid scan frequency setting"})
	}

	autoScanEnabled, err := strconv.ParseBool(autoScanSetting)
	if err != nil {
		return c.JSON(http.StatusInternalServerError, map[string]string{"error": "invalid auto scan setting"})
	}

	return c.JSON(http.StatusOK, ScanSettingsResponse{
		ScanPollIntervalSeconds: int32(scanFrequency),
		AutoScanEnabled:         autoScanEnabled,
	})
}
