package handlers

import (
	"bookhoard/internal/database"
	"bookhoard/internal/services"
	wsync "bookhoard/internal/sync"
	"bookhoard/internal/utils"
	"encoding/json"
	"errors"
	"fmt"
	"net/http"
	"strconv"
	"strings"
	"time"

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

type CollectionHandler struct {
	db                *database.Queries
	collectionService *services.CollectionService
	libraryService    *services.LibraryService
	connManager       *wsync.ConnectionManager
}

func NewCollectionHandler(db *database.Queries, libraryService *services.LibraryService, connManager *wsync.ConnectionManager) *CollectionHandler {
	return &CollectionHandler{
		db:                db,
		collectionService: services.NewCollectionService(db),
		libraryService:    libraryService,
		connManager:       connManager,
	}
}

type CreateCollectionRequest struct {
	Name            string                 `form:"name" json:"name" validate:"required"`
	Description     string                 `form:"description" json:"description"`
	Color           string                 `form:"color" json:"color"`
	Icon            string                 `form:"icon" json:"icon"`
	AutoAssignRules []services.Rule        `form:"auto_assign_rules" json:"auto_assign_rules"`
	ViewSettings    map[string]interface{} `form:"view_settings" json:"view_settings"`
}

type UpdateCollectionRequest struct {
	Name            string                 `form:"name" json:"name" validate:"required"`
	Description     string                 `form:"description" json:"description"`
	Color           string                 `form:"color" json:"color"`
	Icon            string                 `form:"icon" json:"icon"`
	AutoAssignRules []services.Rule        `form:"auto_assign_rules" json:"auto_assign_rules"`
	ViewSettings    map[string]interface{} `form:"view_settings" json:"view_settings"`
}

type AddBooksRequest struct {
	BookIDs []string `json:"book_ids" validate:"required"`
}

type CreateDeviceMappingRequest struct {
	CollectionID    string `json:"collection_id" validate:"required"`
	DeviceShelfName string `json:"device_shelf_name" validate:"required"`
	SyncDirection   string `json:"sync_direction" validate:"required,oneof=bidirectional book_to_device device_to_book none"`
}

type UpdateDeviceMappingRequest struct {
	DeviceShelfName string `json:"device_shelf_name" validate:"required"`
	SyncDirection   string `json:"sync_direction" validate:"required,oneof=bidirectional book_to_device device_to_book none"`
}

type BookInfo struct {
	MediaItemID    string `json:"media_item_id"`
	Title          string `json:"title"`
	Author         string `json:"author"`
	CoverImagePath string `json:"cover_image_path"`
	HasConflict    bool   `json:"has_conflict"`
}

type SectionData struct {
	ID           string     `json:"id"`
	CollectionID string     `json:"collection_id"`
	IsSystem     bool       `json:"is_system"`
	Title        string     `json:"title"`
	Description  string     `json:"description"`
	Icon         string     `json:"icon"`
	Items        []BookInfo `json:"items"`
	ViewAllURL   string     `json:"view_all_url"`
	Priority     int        `json:"priority"`
}

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

	var req CreateCollectionRequest
	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()})
	}

	collection, err := h.collectionService.CreateCollection(
		c.Request().Context(),
		userUUID,
		req.Name,
		req.Description,
		req.Color,
		req.Icon,
		req.AutoAssignRules,
		req.ViewSettings,
	)
	if err != nil {
		return c.JSON(http.StatusInternalServerError, map[string]string{"error": err.Error()})
	}

	bookCount := int32(0)
	response := map[string]interface{}{
		"id":                uuid.UUID(collection.ID.Bytes).String(),
		"user_id":           uuid.UUID(collection.UserID.Bytes).String(),
		"name":              collection.Name,
		"description":       textToString(collection.Description),
		"color":             textToString(collection.Color),
		"icon":              textToString(collection.Icon),
		"auto_assign_rules": collection.AutoAssignRules,
		"view_settings":     collection.ViewSettings,
		"book_count":        bookCount,
		"created_at":        collection.CreatedAt.Time.String(),
	}

	// 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.StatusCreated, response)
}

func (h *CollectionHandler) GetCollections(c *echo.Context) error {
	includeAuto := c.QueryParam("include_auto") == "true"
	sortBy := c.QueryParam("sort_by")
	libraryID := c.QueryParam("library_id")

	collections, err := h.GetCollectionsData(c)
	if err != nil {
		return c.JSON(http.StatusInternalServerError, map[string]string{"error": err.Error()})
	}

	type CollectionResponse struct {
		ID              uuid.UUID       `json:"id"`
		Name            string          `json:"name"`
		Description     string          `json:"description"`
		Color           string          `json:"color"`
		Icon            string          `json:"icon"`
		AutoAssignRules json.RawMessage `json:"auto_assign_rules"`
		CreatedAt       string          `json:"created_at"`
		BookCount       int             `json:"book_count"`
	}

	response := make([]CollectionResponse, 0, len(collections))
	for _, col := range collections {
		if !includeAuto && len(col.AutoAssignRules) > 0 {
			continue
		}

		bookCount := 0
		if libraryID != "" {
			libUUID, libErr := uuid.Parse(libraryID)
			if libErr == nil {
				items, countErr := h.db.GetCollectionItemsForDashboard(c.Request().Context(), database.GetCollectionItemsForDashboardParams{
					CollectionID: pgtype.UUID{Bytes: col.ID.Bytes, Valid: true},
					LibraryID:    pgtype.UUID{Bytes: libUUID, Valid: true},
					Limit:        pgtype.Int4{Int32: 10000, Valid: true},
				})
				if countErr == nil {
					for _, item := range items {
						if !item.Excluded.Valid || !item.Excluded.Bool {
							bookCount++
						}
					}
				}
			}
		}

		response = append(response, CollectionResponse{
			ID:              col.ID.Bytes,
			Name:            col.Name,
			Description:     textToString(col.Description),
			Color:           textToString(col.Color),
			Icon:            textToString(col.Icon),
			AutoAssignRules: col.AutoAssignRules,
			CreatedAt:       col.CreatedAt.Time.String(),
			BookCount:       bookCount,
		})
	}

	if sortBy == "name" {
		for i := 0; i < len(response); i++ {
			for j := i + 1; j < len(response); j++ {
				if response[i].Name > response[j].Name {
					response[i], response[j] = response[j], response[i]
				}
			}
		}
	}

	return c.JSON(http.StatusOK, map[string]interface{}{
		"collections": response,
		"total":       len(response),
	})
}

func (h *CollectionHandler) GetCollection(c *echo.Context) error {
	collectionID, err := uuid.Parse(c.Param("id"))
	if err != nil {
		return c.JSON(http.StatusBadRequest, map[string]string{"error": "invalid collection id"})
	}

	collection, err := h.GetCollectionData(c, collectionID)
	if err != nil {
		return c.JSON(http.StatusNotFound, map[string]string{"error": "collection not found"})
	}

	libraryID := c.QueryParam("library_id")
	var bookList []BookInfo

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

	if collection.QueryType.Valid && collection.QueryType.String != "" {
		user := c.Get("user").(database.Users)
		userUUID := uuid.UUID(user.ID.Bytes)
		dashboardSvc := services.NewDashboardService(h.db)
		sections, secErr := dashboardSvc.GetDashboardSections(c.Request().Context(), userUUID, libUUID, 1000, []string{}, []string{})
		if secErr != nil {
			return c.JSON(http.StatusInternalServerError, map[string]string{"error": secErr.Error()})
		}
		for _, section := range sections {
			if section.CollectionID == collectionID {
				bookCards := make([]BookInfo, len(section.Items))
				for i, item := range section.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),
					}
				}
				bookList = bookCards
				break
			}
		}
	} else if libUUID.Valid {
		collItems, collErr := h.db.GetCollectionItemsForDashboard(c.Request().Context(),
			database.GetCollectionItemsForDashboardParams{
				CollectionID: pgtype.UUID{Bytes: collectionID, Valid: true},
				LibraryID:    libUUID,
				Limit:        pgtype.Int4{Int32: 10000, Valid: true},
			})
		if collErr != nil {
			bookList = []BookInfo{}
		} else {
			var validItems []database.GetCollectionItemsForDashboardRow
			for _, item := range collItems {
				if !item.Excluded.Valid || !item.Excluded.Bool {
					validItems = append(validItems, item)
				}
			}
			bookCards := make([]BookInfo, len(validItems))
			for i, item := range validItems {
				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),
				}
			}
			bookList = bookCards
		}
	} else {
		books, booksErr := h.GetCollectionBooksData(c, collectionID)
		if booksErr != nil {
			return c.JSON(http.StatusInternalServerError, map[string]string{"error": booksErr.Error()})
		}
		bookList = make([]BookInfo, 0, len(books))
		for _, book := range books {
			bookList = append(bookList, BookInfo{
				MediaItemID:    uuid.UUID(book.MediaItemID.Bytes).String(),
				Title:          book.Title,
				Author:         textToString(book.Author),
				CoverImagePath: utils.ResolveMediaURL(book.LibraryID, book.CoverImagePath),
			})
		}
	}

	var viewSettings map[string]interface{}
	if len(collection.ViewSettings) > 0 {
		err := json.Unmarshal(collection.ViewSettings, &viewSettings)
		if err != nil {
			return err
		}
	}

	return c.JSON(http.StatusOK, map[string]interface{}{
		"id":                uuid.UUID(collection.ID.Bytes).String(),
		"user_id":           uuid.UUID(collection.UserID.Bytes).String(),
		"name":              collection.Name,
		"description":       textToString(collection.Description),
		"color":             textToString(collection.Color),
		"icon":              textToString(collection.Icon),
		"auto_assign_rules": json.RawMessage(collection.AutoAssignRules),
		"view_settings":     viewSettings,
		"books":             bookList,
		"book_count":        len(bookList),
		"created_at":        collection.CreatedAt.Time.String(),
	})
}

func (h *CollectionHandler) UpdateCollection(c *echo.Context) error {
	collectionID, err := uuid.Parse(c.Param("id"))
	if err != nil {
		return c.JSON(http.StatusBadRequest, map[string]string{"error": "invalid collection id"})
	}

	var req UpdateCollectionRequest
	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()})
	}

	collection, err := h.collectionService.UpdateCollection(
		c.Request().Context(),
		collectionID,
		req.Name,
		req.Description,
		req.Color,
		req.Icon,
		req.AutoAssignRules,
		req.ViewSettings,
	)
	if err != nil {
		return c.JSON(http.StatusInternalServerError, map[string]string{"error": err.Error()})
	}

	response := map[string]interface{}{
		"id":                uuid.UUID(collection.ID.Bytes).String(),
		"user_id":           uuid.UUID(collection.UserID.Bytes).String(),
		"name":              collection.Name,
		"description":       textToString(collection.Description),
		"color":             textToString(collection.Color),
		"icon":              textToString(collection.Icon),
		"auto_assign_rules": json.RawMessage(collection.AutoAssignRules),
		"view_settings":     json.RawMessage(collection.ViewSettings),
		"created_at":        collection.CreatedAt.Time.String(),
	}

	// 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.StatusCreated, response)
}

func (h *CollectionHandler) DeleteCollection(c *echo.Context) error {
	collectionID, err := uuid.Parse(c.Param("id"))
	if err != nil {
		return c.JSON(http.StatusBadRequest, map[string]string{"error": "invalid collection id"})
	}

	err = h.collectionService.DeleteCollection(c.Request().Context(), collectionID)
	if err != nil {
		return c.JSON(http.StatusInternalServerError, map[string]string{"error": err.Error()})
	}

	if c.Request().Header.Get("HX-Request") == "true" {
		html := `<div class="text-green-500">Collection deleted! Redirecting...</div><script>window.location.href = '/collections';</script>`
		return c.HTML(http.StatusOK, html)
	}

	return c.NoContent(http.StatusNoContent)
}

func (h *CollectionHandler) AddBooks(c *echo.Context) error {
	collectionID, err := uuid.Parse(c.Param("id"))
	if err != nil {
		return c.JSON(http.StatusBadRequest, map[string]string{"error": "invalid collection id"})
	}

	user := c.Get("user").(database.Users)
	userUUID := uuid.UUID(user.ID.Bytes)

	var req AddBooksRequest
	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()})
	}

	addedCount := 0
	var addedBookIDs []string
	for _, bookIDStr := range req.BookIDs {
		bookID, err := uuid.Parse(bookIDStr)
		if err != nil {
			continue
		}
		err = h.collectionService.AddBookToCollection(c.Request().Context(), collectionID, bookID, userUUID)
		if err == nil {
			addedCount++
			addedBookIDs = append(addedBookIDs, bookID.String())
		}
	}

	if addedCount > 0 && h.connManager != nil {
		h.connManager.BroadcastToUser(userUUID.String(), wsync.BroadcastMessage{
			Type:      "collection_updated",
			Timestamp: time.Now().Format(time.RFC3339),
			Data: map[string]interface{}{
				"collection_id": collectionID.String(),
				"action":        "books_added",
				"book_ids":      addedBookIDs,
				"count":         addedCount,
			},
		})
	}

	return c.NoContent(http.StatusNoContent)
}

func (h *CollectionHandler) RemoveBook(c *echo.Context) error {
	collectionID, err := uuid.Parse(c.Param("id"))
	if err != nil {
		return c.JSON(http.StatusBadRequest, map[string]string{"error": "invalid collection id"})
	}

	bookID, err := uuid.Parse(c.Param("bookId"))
	if err != nil {
		return c.JSON(http.StatusBadRequest, map[string]string{"error": "invalid book id"})
	}

	// Get user from context for WebSocket broadcast
	user := c.Get("user").(database.Users)
	userUUID := uuid.UUID(user.ID.Bytes)

	err = h.collectionService.RemoveBookFromCollection(c.Request().Context(), collectionID, bookID)
	if err != nil {
		return c.JSON(http.StatusInternalServerError, map[string]string{"error": err.Error()})
	}

	// Broadcast to user's other devices via WebSocket
	if h.connManager != nil {
		h.connManager.BroadcastToUser(userUUID.String(), wsync.BroadcastMessage{
			Type:      "collection_updated",
			Timestamp: time.Now().Format(time.RFC3339),
			Data: map[string]interface{}{
				"collection_id": collectionID.String(),
				"action":        "book_removed",
				"book_id":       bookID.String(),
			},
		})
	}

	return c.NoContent(http.StatusNoContent)
}

type BulkRemoveBooksRequest struct {
	BookIDs []string `json:"book_ids" validate:"required"`
}

func (h *CollectionHandler) BulkRemoveBooks(c *echo.Context) error {
	collectionID, err := uuid.Parse(c.Param("id"))
	if err != nil {
		return c.JSON(http.StatusBadRequest, map[string]string{"error": "invalid collection id"})
	}

	user := c.Get("user").(database.Users)
	userUUID := uuid.UUID(user.ID.Bytes)

	var req BulkRemoveBooksRequest
	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()})
	}

	removedCount := 0
	var removedBookIDs []string
	for _, bookIDStr := range req.BookIDs {
		bookID, err := uuid.Parse(bookIDStr)
		if err != nil {
			continue
		}

		err = h.collectionService.RemoveBookFromCollection(c.Request().Context(), collectionID, bookID)
		if err == nil {
			removedCount++
			removedBookIDs = append(removedBookIDs, bookID.String())
		}
	}

	if removedCount > 0 && h.connManager != nil {
		h.connManager.BroadcastToUser(userUUID.String(), wsync.BroadcastMessage{
			Type:      "collection_updated",
			Timestamp: time.Now().Format(time.RFC3339),
			Data: map[string]interface{}{
				"collection_id": collectionID.String(),
				"action":        "books_removed",
				"book_ids":      removedBookIDs,
				"count":         removedCount,
			},
		})
	}

	return c.JSON(http.StatusOK, map[string]interface{}{
		"removed": removedCount,
		"total":   len(req.BookIDs),
	})
}

func (h *CollectionHandler) GetDeviceMappings(c *echo.Context) error {
	deviceID, err := uuid.Parse(c.Param("id"))
	if err != nil {
		return c.JSON(http.StatusBadRequest, map[string]string{"error": "invalid device id"})
	}

	mappings, err := h.collectionService.GetDeviceShelfMappings(c.Request().Context(), deviceID)
	if err != nil {
		return c.JSON(http.StatusInternalServerError, map[string]string{"error": err.Error()})
	}

	type MappingResponse struct {
		ID              uuid.UUID `json:"id"`
		CollectionID    uuid.UUID `json:"collection_id"`
		CollectionName  string    `json:"collection_name"`
		DeviceShelfName string    `json:"device_shelf_name"`
		SyncDirection   string    `json:"sync_direction"`
		CreatedAt       string    `json:"created_at"`
	}

	response := make([]MappingResponse, 0, len(mappings))
	for _, m := range mappings {
		response = append(response, MappingResponse{
			ID:              m.ID.Bytes,
			CollectionID:    m.CollectionID.Bytes,
			CollectionName:  m.CollectionName,
			DeviceShelfName: textToString(m.DeviceShelfName),
			SyncDirection:   textToString(m.SyncDirection),
			CreatedAt:       m.CreatedAt.Time.String(),
		})
	}

	return c.JSON(http.StatusOK, map[string]interface{}{
		"device_id": deviceID.String(),
		"mappings":  response,
		"total":     len(response),
	})
}

func (h *CollectionHandler) CreateDeviceMapping(c *echo.Context) error {
	deviceID, err := uuid.Parse(c.Param("id"))
	if err != nil {
		return c.JSON(http.StatusBadRequest, map[string]string{"error": "invalid device id"})
	}

	var req CreateDeviceMappingRequest
	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()})
	}

	collectionID, err := uuid.Parse(req.CollectionID)
	if err != nil {
		return c.JSON(http.StatusBadRequest, map[string]string{"error": "invalid collection id"})
	}

	mapping, err := h.collectionService.CreateDeviceShelfMapping(
		c.Request().Context(),
		collectionID,
		deviceID,
		req.DeviceShelfName,
		req.SyncDirection,
	)
	if err != nil {
		return c.JSON(http.StatusInternalServerError, map[string]string{"error": err.Error()})
	}

	return c.JSON(http.StatusCreated, map[string]interface{}{
		"id":                uuid.UUID(mapping.ID.Bytes).String(),
		"collection_id":     uuid.UUID(mapping.CollectionID.Bytes).String(),
		"device_id":         uuid.UUID(mapping.DeviceID.Bytes).String(),
		"device_shelf_name": textToString(mapping.DeviceShelfName),
		"sync_direction":    textToString(mapping.SyncDirection),
		"created_at":        mapping.CreatedAt.Time.String(),
	})
}

func (h *CollectionHandler) UpdateDeviceMapping(c *echo.Context) error {
	mappingID, err := uuid.Parse(c.Param("mappingId"))
	if err != nil {
		return c.JSON(http.StatusBadRequest, map[string]string{"error": "invalid mapping id"})
	}

	var req UpdateDeviceMappingRequest
	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()})
	}

	mapping, err := h.collectionService.UpdateDeviceShelfMapping(
		c.Request().Context(),
		mappingID,
		req.DeviceShelfName,
		req.SyncDirection,
	)
	if err != nil {
		return c.JSON(http.StatusInternalServerError, map[string]string{"error": err.Error()})
	}

	return c.JSON(http.StatusOK, map[string]interface{}{
		"id":                uuid.UUID(mapping.ID.Bytes).String(),
		"collection_id":     uuid.UUID(mapping.CollectionID.Bytes).String(),
		"device_id":         uuid.UUID(mapping.DeviceID.Bytes).String(),
		"device_shelf_name": textToString(mapping.DeviceShelfName),
		"sync_direction":    textToString(mapping.SyncDirection),
		"created_at":        mapping.CreatedAt.Time.String(),
	})
}

func (h *CollectionHandler) DeleteDeviceMapping(c *echo.Context) error {
	mappingID, err := uuid.Parse(c.Param("mappingId"))
	if err != nil {
		return c.JSON(http.StatusBadRequest, map[string]string{"error": "invalid mapping id"})
	}

	err = h.collectionService.DeleteDeviceShelfMapping(c.Request().Context(), mappingID)
	if err != nil {
		return c.JSON(http.StatusInternalServerError, map[string]string{"error": err.Error()})
	}

	return c.NoContent(http.StatusNoContent)
}

func (h *CollectionHandler) GetBookCollections(c *echo.Context) error {
	bookID, err := uuid.Parse(c.Param("id"))
	if err != nil {
		return c.JSON(http.StatusBadRequest, map[string]string{"error": "invalid book id"})
	}

	collections, err := h.collectionService.GetBookCollections(c.Request().Context(), bookID)
	if err != nil {
		return c.JSON(http.StatusInternalServerError, map[string]string{"error": err.Error()})
	}

	type CollectionResponse struct {
		ID          uuid.UUID `json:"id"`
		Name        string    `json:"name"`
		Description string    `json:"description"`
		Color       string    `json:"color"`
		Icon        string    `json:"icon"`
	}

	response := make([]CollectionResponse, 0, len(collections))
	for _, col := range collections {
		response = append(response, CollectionResponse{
			ID:          col.ID.Bytes,
			Name:        col.Name,
			Description: textToString(col.Description),
			Color:       textToString(col.Color),
			Icon:        textToString(col.Icon),
		})
	}

	return c.JSON(http.StatusOK, map[string]interface{}{
		"book_id":     bookID.String(),
		"collections": response,
		"total":       len(response),
	})
}

func textToString(t pgtype.Text) string {
	if t.Valid {
		return t.String
	}
	return ""
}

func (h *CollectionHandler) GetCollectionsData(c *echo.Context) ([]database.Collections, error) {
	user := c.Get("user").(database.Users)
	userUUID := uuid.UUID(user.ID.Bytes)
	return h.collectionService.GetUserCollections(c.Request().Context(), userUUID)
}

func (h *CollectionHandler) GetCollectionData(c *echo.Context, collectionID uuid.UUID) (database.Collections, error) {
	return h.collectionService.GetCollection(c.Request().Context(), collectionID)
}

func (h *CollectionHandler) GetCollectionBooksData(c *echo.Context, collectionID uuid.UUID) ([]database.GetCollectionItemsRow, error) {
	return h.collectionService.GetCollectionBooks(c.Request().Context(), collectionID)
}

func (h *CollectionHandler) GetDeviceMappingsData(c *echo.Context, deviceID uuid.UUID) ([]database.GetDeviceShelfMappingsRow, error) {
	return h.collectionService.GetDeviceShelfMappings(c.Request().Context(), deviceID)
}

func (h *CollectionHandler) GetUserCollectionsList(c *echo.Context) ([]database.Collections, error) {
	user := c.Get("user").(database.Users)
	userUUID := uuid.UUID(user.ID.Bytes)
	return h.collectionService.GetUserCollections(c.Request().Context(), userUUID)
}

type TestRulesRequest struct {
	Rules []map[string]interface{} `json:"rules" validate:"required"`
}

type BookMatch struct {
	MediaItemID    string `json:"media_item_id"`
	Title          string `json:"title"`
	Author         string `json:"author"`
	CoverImagePath string `json:"cover_image_path"`
	MatchReason    string `json:"match_reason"`
}

func (h *CollectionHandler) TestRules(c *echo.Context) error {
	var req TestRulesRequest
	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()})
	}

	mediaItems, err := h.db.ListMediaItems(c.Request().Context(), database.ListMediaItemsParams{
		Limit:  1000,
		Offset: 0,
	})
	if err != nil {
		return c.JSON(http.StatusInternalServerError, map[string]string{"error": "failed to load media items"})
	}

	var matches []BookMatch
	for _, item := range mediaItems {
		matchReason := h.checkRulesAgainstBook(item, req.Rules)
		if matchReason != "" {
			author := ""
			if item.Author.Valid {
				author = item.Author.String
			}

			matches = append(matches, BookMatch{
				MediaItemID:    uuid.UUID(item.ID.Bytes).String(),
				Title:          item.Title,
				Author:         author,
				CoverImagePath: utils.ResolveMediaURL(item.LibraryID, item.CoverImagePath),
				MatchReason:    matchReason,
			})
		}
	}

	return c.JSON(http.StatusOK, map[string]interface{}{
		"matches": matches,
		"total":   len(matches),
	})
}

func (h *CollectionHandler) checkRulesAgainstBook(item database.ListMediaItemsRow, rules []map[string]interface{}) string {
	for _, rule := range rules {
		field, _ := rule["field"].(string)
		operator, _ := rule["operator"].(string)
		value, _ := rule["value"].(string)

		if h.evaluateRule(item, field, operator, value) {
			return fmt.Sprintf("Matched rule: %s %s %s", field, operator, value)
		}
	}
	return ""
}

func (h *CollectionHandler) evaluateRule(item database.ListMediaItemsRow, field, operator, value string) bool {
	var itemValue string

	switch field {
	case "genre":
		if item.Genre.Valid {
			itemValue = item.Genre.String
		}
	case "series":
		if item.Series.Valid {
			itemValue = item.Series.String
		}
	case "author":
		if item.Author.Valid {
			itemValue = item.Author.String
		}
	case "language":
		if item.Language.Valid {
			itemValue = item.Language.String
		}
	case "publisher":
		if item.Publisher.Valid {
			itemValue = item.Publisher.String
		}
	case "copyright_year":
		if item.CopyrightYear.Valid {
			itemValue = fmt.Sprintf("%d", item.CopyrightYear.Int32)
		}
	case "tags":
		if len(item.Tags) > 0 {
			itemValue = strings.Join(item.Tags, ", ")
		}
	}

	return h.compareValues(itemValue, operator, value)
}

func (h *CollectionHandler) compareValues(itemValue, operator, ruleValue string) bool {
	switch operator {
	case "equals":
		return itemValue == ruleValue
	case "not_equals":
		return itemValue != ruleValue
	case "contains":
		return strings.Contains(strings.ToLower(itemValue), strings.ToLower(ruleValue))
	case "not_contains":
		return !strings.Contains(strings.ToLower(itemValue), strings.ToLower(ruleValue))
	case "starts_with":
		return strings.HasPrefix(strings.ToLower(itemValue), strings.ToLower(ruleValue))
	case "ends_with":
		return strings.HasSuffix(strings.ToLower(itemValue), strings.ToLower(ruleValue))
	case "greater_than":
		itemNum, err1 := strconv.Atoi(itemValue)
		ruleNum, err2 := strconv.Atoi(ruleValue)
		if err1 != nil || err2 != nil {
			return false
		}
		return itemNum > ruleNum
	case "less_than":
		itemNum, err1 := strconv.Atoi(itemValue)
		ruleNum, err2 := strconv.Atoi(ruleValue)
		if err1 != nil || err2 != nil {
			return false
		}
		return itemNum < ruleNum
	}
	return false
}

// Bulk Operations

// POST /api/collections/bulk-add-books
// Bulk add books to multiple collections
func (h *CollectionHandler) HandleBulkAddBooks(c *echo.Context) error {
	user := c.Get("user").(database.Users)
	userUUID := uuid.UUID(user.ID.Bytes)

	var req struct {
		Operations []struct {
			CollectionID string   `json:"collection_id" validate:"required"`
			BookIDs      []string `json:"book_ids" validate:"required"`
		} `json:"operations" validate:"required"`
	}

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

	if len(req.Operations) == 0 {
		return c.JSON(http.StatusBadRequest, map[string]string{"error": "operations required"})
	}

	results := make([]map[string]interface{}, 0)
	successCount := 0
	failedCount := 0

	for _, op := range req.Operations {
		collectionID, err := uuid.Parse(op.CollectionID)
		if err != nil {
			results = append(results, map[string]interface{}{
				"collection_id": op.CollectionID,
				"status":        "error",
				"error":         "Invalid collection UUID",
			})
			failedCount++
			continue
		}

		for _, bookIDStr := range op.BookIDs {
			bookID, err := uuid.Parse(bookIDStr)
			if err != nil {
				results = append(results, map[string]interface{}{
					"collection_id": op.CollectionID,
					"book_id":       bookIDStr,
					"status":        "error",
					"error":         "Invalid book UUID",
				})
				failedCount++
				continue
			}

			collectionUUID := pgtype.UUID{Bytes: collectionID, Valid: true}
			bookUUID := pgtype.UUID{Bytes: bookID, Valid: true}

			_, err = h.db.AddBookToCollection(c.Request().Context(), database.AddBookToCollectionParams{
				CollectionID:  collectionUUID,
				MediaItemID:   bookUUID,
				AddedByUserID: pgtype.UUID{Bytes: user.ID.Bytes, Valid: true},
			})

			if err != nil {
				results = append(results, map[string]interface{}{
					"collection_id": op.CollectionID,
					"book_id":       bookIDStr,
					"status":        "error",
					"error":         err.Error(),
				})
				failedCount++
				continue
			}

			results = append(results, map[string]interface{}{
				"collection_id": op.CollectionID,
				"book_id":       bookIDStr,
				"status":        "success",
			})
			successCount++
		}
	}

	if successCount > 0 && h.connManager != nil {
		h.connManager.BroadcastToUser(userUUID.String(), wsync.BroadcastMessage{
			Type:      "collection_updated",
			Timestamp: time.Now().Format(time.RFC3339),
			Data: map[string]interface{}{
				"action": "books_added_bulk",
				"count":  successCount,
			},
		})
	}

	return c.JSON(http.StatusOK, map[string]interface{}{
		"results": results,
		"total":   len(results),
		"added":   successCount,
		"failed":  failedCount,
	})
}

func (h *CollectionHandler) PreviewCollection(c *echo.Context) error {
	user := c.Get("user")
	if user == nil {
		return c.JSON(http.StatusUnauthorized, map[string]string{"error": "unauthorized"})
	}

	var req struct {
		LibraryID     string          `json:"library_id"`
		Rules         []services.Rule `json:"rules"`
		ManualBookIDs []string        `json:"manual_book_ids"`
		Limit         int             `json:"limit"`
	}

	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"})
	}

	_, err = h.db.GetLibrary(c.Request().Context(), pgtype.UUID{Bytes: libUUID, Valid: true})
	if err != nil {
		if errors.Is(err, pgx.ErrNoRows) {
			return c.JSON(http.StatusBadRequest, map[string]string{"error": "library not found"})
		}
		return c.JSON(http.StatusInternalServerError, map[string]string{"error": err.Error()})
	}

	if req.Limit <= 0 || req.Limit > 100 {
		req.Limit = 20
	}

	allItems, err := h.db.GetLibraryItems(c.Request().Context(), pgtype.UUID{Bytes: libUUID, Valid: true})
	if err != nil {
		return c.JSON(http.StatusInternalServerError, map[string]string{"error": "Failed to load library items"})
	}

	var matchedItems []database.MediaItems
	for _, item := range allItems {
		listItem := mediaItemsToListMediaItemsRow(item)
		evaluations := h.collectionService.EvaluateRules(listItem, req.Rules)
		for _, eval := range evaluations {
			if eval.Matches {
				matchedItems = append(matchedItems, item)
				break
			}
		}
	}

	for _, bookID := range req.ManualBookIDs {
		bookUUID, err := uuid.Parse(bookID)
		if err != nil {
			continue
		}

		for _, item := range allItems {
			itemUUID, _ := uuid.FromBytes(item.ID.Bytes[0:16])
			if itemUUID == bookUUID {
				alreadyAdded := false
				for _, added := range matchedItems {
					addedUUID, _ := uuid.FromBytes(added.ID.Bytes[0:16])
					if addedUUID == bookUUID {
						alreadyAdded = true
						break
					}
				}
				if !alreadyAdded {
					matchedItems = append(matchedItems, item)
				}
				break
			}
		}
	}

	if len(matchedItems) > req.Limit {
		matchedItems = matchedItems[:req.Limit]
	}

	bookCards := make([]BookInfo, len(matchedItems))
	for i, item := range matchedItems {
		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),
		}
	}

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

func mediaItemsToListMediaItemsRow(item database.MediaItems) database.ListMediaItemsRow {
	return database.ListMediaItemsRow{
		ID:                 item.ID,
		LibraryID:          item.LibraryID,
		Title:              item.Title,
		Author:             item.Author,
		Isbn:               item.Isbn,
		Description:        item.Description,
		FilePath:           utils.ResolveMediaURL(item.LibraryID, pgtype.Text{String: item.FilePath, Valid: true}),
		FileSize:           item.FileSize,
		MimeType:           item.MimeType,
		CoverImagePath:     pgtype.Text{String: utils.ResolveMediaURL(item.LibraryID, item.CoverImagePath), Valid: true},
		Series:             item.Series,
		SeriesNumber:       item.SeriesNumber,
		Tags:               item.Tags,
		Asin:               item.Asin,
		DatePublished:      item.DatePublished,
		Publisher:          item.Publisher,
		Contributors:       item.Contributors,
		Language:           item.Language,
		Edition:            item.Edition,
		PageCount:          item.PageCount,
		Genre:              item.Genre,
		CopyrightYear:      item.CopyrightYear,
		GoodreadsID:        item.GoodreadsID,
		OpenlibraryID:      item.OpenlibraryID,
		GoogleBooksID:      item.GoogleBooksID,
		AddedByAdminID:     item.AddedByAdminID,
		CreatedAt:          item.CreatedAt,
		UpdatedAt:          item.UpdatedAt,
		FormatGroup:        item.FormatGroup,
		FormatMimetype:     item.FormatMimetype,
		IsReflowable:       item.IsReflowable,
		HasFixedLayout:     item.HasFixedLayout,
		TotalCharacters:    item.TotalCharacters,
		ChapterCount:       item.ChapterCount,
		EntitlementID:      item.EntitlementID,
		RevisionNumber:     item.RevisionNumber,
		KoboContentID:      item.KoboContentID,
		KoboMetadata:       item.KoboMetadata,
		TagsSearch:         item.TagsSearch,
		ContributorsSearch: item.ContributorsSearch,
		FileSha256:         item.FileSha256,
		OpfIdentifier:      item.OpfIdentifier,
		OpfUuid:            item.OpfUuid,
		HashConfidence:     item.HashConfidence,
	}
}
