package handlers

import (
	"bookhoard/internal/database"
	"bookhoard/internal/services"
	"bookhoard/internal/utils"
	"context"
	"log"
	"net/http"
	"strconv"

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

type DashboardHandler struct {
	db               *database.Queries
	dashboardService *services.DashboardService
}

func NewDashboardHandler(db *database.Queries) *DashboardHandler {
	return &DashboardHandler{
		db:               db,
		dashboardService: services.NewDashboardService(db),
	}
}

func (h *DashboardHandler) GetSections(c *echo.Context) error {
	user := c.Get("user").(database.Users)
	userUUID := uuid.UUID(user.ID.Bytes)

	libraryID := c.QueryParam("library_id")
	var libUUID pgtype.UUID
	if libraryID != "" {
		parsed, err := uuid.Parse(libraryID)
		if err != nil {
			return c.JSON(http.StatusBadRequest, map[string]string{"error": "invalid library_id"})
		}
		libUUID = pgtype.UUID{Bytes: parsed, Valid: true}
	}

	prefs, _ := h.dashboardService.GetDashboardPreferences(c.Request().Context(), userUUID, libUUID)

	limit := 20
	if prefs.ItemsPerSection.Valid {
		limit = int(prefs.ItemsPerSection.Int32)
	}
	if limitStr := c.QueryParam("limit"); limitStr != "" {
		if l, err := strconv.Atoi(limitStr); err == nil && l > 0 && l <= 100 {
			limit = l
		}
	}

	sections, err := h.dashboardService.GetDashboardSections(
		c.Request().Context(),
		userUUID,
		libUUID,
		limit,
		prefs.CollectionOrder,
		prefs.HiddenCollections,
	)
	if err != nil {
		return c.JSON(http.StatusInternalServerError, map[string]string{"error": "Failed to load dashboard sections"})
	}

	sectionData := BuildSections(sections, libraryID)
	sectionData = MarkActiveConflictsSections(c.Request().Context(), h.db, user.ID, sectionData)

	return c.JSON(http.StatusOK, map[string]interface{}{"sections": sectionData})
}

func (h *DashboardHandler) UpdatePreferences(c *echo.Context) error {
	user := c.Get("user").(database.Users)
	userUUID := uuid.UUID(user.ID.Bytes)

	var req struct {
		LibraryID         string   `json:"library_id"`
		HiddenCollections []string `json:"hidden_collections"`
		CollectionOrder   []string `json:"collection_order"`
		ItemsPerSection   int      `json:"items_per_section"`
	}

	if err := c.Bind(&req); err != nil {
		return c.JSON(http.StatusBadRequest, map[string]string{"error": "Invalid request"})
	}

	libUUID, err := uuid.Parse(req.LibraryID)
	if err != nil {
		return c.JSON(http.StatusBadRequest, map[string]string{"error": "invalid library_id"})
	}

	// Sanitize preferences (remove duplicates)
	cleanHidden, cleanOrder := h.dashboardService.SanitizeDashboardPreferences(req.HiddenCollections, req.CollectionOrder)
	req.HiddenCollections = cleanHidden
	req.CollectionOrder = cleanOrder

	prefs, err := h.dashboardService.UpsertDashboardPreferences(c.Request().Context(), database.UpsertDashboardPreferencesParams{
		UserID:            pgtype.UUID{Bytes: userUUID, Valid: true},
		LibraryID:         pgtype.UUID{Bytes: libUUID, Valid: true},
		HiddenCollections: req.HiddenCollections,
		CollectionOrder:   req.CollectionOrder,
		ItemsPerSection:   pgtype.Int4{Int32: int32(req.ItemsPerSection), Valid: true},
	})

	if err != nil {
		return c.JSON(http.StatusInternalServerError, map[string]string{"error": "Failed to save preferences"})
	}

	return c.JSON(http.StatusOK, prefs)
}

func (h *DashboardHandler) RestoreSystemCollection(c *echo.Context) error {
	user := c.Get("user").(database.Users)
	userUUID := uuid.UUID(user.ID.Bytes)

	var req struct {
		CollectionName string `form:"collection_name" json:"collection_name"`
		ResetType      string `form:"reset_type" json:"reset_type"`
	}

	if err := c.Bind(&req); err != nil {
		return c.JSON(http.StatusBadRequest, map[string]string{"error": "Invalid request"})
	}

	if req.CollectionName == "" {
		return c.JSON(http.StatusBadRequest, map[string]string{"error": "collection_name required"})
	}

	if req.ResetType == "" {
		req.ResetType = "full"
	}

	validCollections := map[string]bool{
		"Continue Reading": true,
		"Recently Added":   true,
		"Recently Read":    true,
		"Not Started":      true,
		"Continue Series":  true,
	}
	if !validCollections[req.CollectionName] {
		return c.JSON(http.StatusBadRequest, map[string]string{"error": "Invalid system collection name"})
	}

	if req.ResetType != "full" && req.ResetType != "keep_books" {
		return c.JSON(http.StatusBadRequest, map[string]string{"error": "reset_type must be 'full' or 'keep_books'"})
	}

	err := h.dashboardService.RestoreSystemCollection(c.Request().Context(), userUUID, req.CollectionName, req.ResetType)
	if err != nil {
		return c.JSON(http.StatusInternalServerError, map[string]string{"error": "Failed to restore system collection"})
	}

	// Add redirect header for HTMX requests
	if c.Request().Header.Get("HX-Request") == "true" {
		c.Response().Header().Set("HX-Redirect", "/collections")
	}

	return c.JSON(http.StatusOK, map[string]string{"message": "System collection restored to defaults"})
}

func BuildSections(sections []services.DashboardSection, currentLibraryID string) []SectionData {
	var result []SectionData

	for _, ds := range sections {
		bookCards := make([]BookInfo, len(ds.Items))
		for i, item := range ds.Items {
			itemUUID, _ := uuid.FromBytes(item.ID.Bytes[0:16])
			bookCards[i] = BookInfo{
				MediaItemID:    itemUUID.String(),
				Title:          item.Title,
				Author:         textToString(item.Author),
				CoverImagePath: utils.ResolveMediaURL(item.LibraryID, item.CoverImagePath),
			}
		}

		result = append(result, SectionData{
			ID:           ds.CollectionName,
			CollectionID: ds.CollectionID.String(),
			IsSystem:     ds.IsSystem,
			Title:        ds.Title,
			Description:  ds.Description,
			Icon:         ds.Icon,
			Items:        bookCards,
			ViewAllURL:   getViewAllURL(ds.CollectionID.String(), currentLibraryID),
			Priority:     ds.Priority,
		})
	}

	return result
}

// activeConflictSet returns the set of media item IDs (as strings) that have an
// active (unresolved) progress sync conflict for the given user. A single query
// is issued; resolved conflicts are filtered out in memory.
func activeConflictSet(ctx context.Context, db *database.Queries, userID pgtype.UUID) map[string]bool {
	conflicts, err := db.ListSyncConflictsByUser(ctx, userID)
	if err != nil {
		return nil
	}
	set := make(map[string]bool, len(conflicts))
	for _, c := range conflicts {
		if c.ResolutionStatus.String == "unresolved" {
			set[uuid.UUID(c.MediaItemID.Bytes).String()] = true
		}
	}
	return set
}

// MarkActiveConflicts stamps HasConflict on each book whose media item has an
// active progress sync conflict for the user. It performs a single query
// regardless of how many books are passed.
func MarkActiveConflicts(ctx context.Context, db *database.Queries, userID pgtype.UUID, books []BookInfo) []BookInfo {
	if len(books) == 0 {
		return books
	}
	set := activeConflictSet(ctx, db, userID)
	for i := range books {
		if set[books[i].MediaItemID] {
			books[i].HasConflict = true
		}
	}
	return books
}

// MarkActiveConflictsSections is the section-aware variant of MarkActiveConflicts,
// used by the dashboard which renders books grouped into sections.
func MarkActiveConflictsSections(ctx context.Context, db *database.Queries, userID pgtype.UUID, sections []SectionData) []SectionData {
	if len(sections) == 0 {
		return sections
	}
	set := activeConflictSet(ctx, db, userID)
	if len(set) == 0 {
		return sections
	}
	for s := range sections {
		for i := range sections[s].Items {
			if set[sections[s].Items[i].MediaItemID] {
				sections[s].Items[i].HasConflict = true
			}
		}
	}
	return sections
}

func getViewAllURL(collectionID string, libraryID string) string {
	if collectionID != "" {
		if libraryID != "" {
			return "/collections/" + collectionID + "?library_id=" + libraryID
		}
		return "/collections/" + collectionID
	}
	return ""
}

func (h *DashboardHandler) GetPreferences(c *echo.Context) error {
	user := c.Get("user").(database.Users)
	userUUID := uuid.UUID(user.ID.Bytes)
	libraryID := c.QueryParam("library_id")

	var libUUID pgtype.UUID
	if libraryID != "" {
		parsed, err := uuid.Parse(libraryID)
		if err != nil {
			return c.JSON(http.StatusBadRequest, map[string]string{"error": "invalid library_id"})
		}
		libUUID = pgtype.UUID{Bytes: parsed, Valid: true}
	}

	prefs, err := h.dashboardService.GetDashboardPreferences(c.Request().Context(), userUUID, libUUID)
	if err != nil {
		// Return default preferences instead of 404 when none exist
		log.Printf("GetDashboardPreferences failed: %v", err)
		prefs = database.UserDashboardPreferences{
			HiddenCollections: []string{},
			CollectionOrder:   []string{},
			ItemsPerSection:   pgtype.Int4{Int32: 20, Valid: true},
		}
	}

	// Normalize nil slices to empty arrays for JSON
	hiddenCollections := prefs.HiddenCollections
	if hiddenCollections == nil {
		hiddenCollections = []string{}
	}
	collectionOrder := prefs.CollectionOrder
	if collectionOrder == nil {
		collectionOrder = []string{}
	}

	return c.JSON(http.StatusOK, map[string]interface{}{
		"hidden_collections": hiddenCollections,
		"collection_order":   collectionOrder,
		"items_per_section":  prefs.ItemsPerSection.Int32,
	})
}
