package handlers

import (
	"bookhoard/internal/database"
	"bookhoard/internal/services"
	"bookhoard/internal/utils"
	"context"
	"fmt"
	"io"
	"mime"
	"net/http"
	"net/url"
	"os"
	"path/filepath"
	"strconv"
	"strings"

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

// CreateMediaItemRequest represents the request for creating a media item
type CreateMediaItemRequest struct {
	LibraryID      uuid.UUID `json:"library_id" validate:"required"`
	Title          string    `json:"title" validate:"required,min=1,max=500"`
	Author         string    `json:"author"`
	ISBN           string    `json:"isbn"`
	Description    string    `json:"description"`
	FilePath       string    `json:"file_path" validate:"required"`
	FileSize       int64     `json:"file_size" validate:"required,min=1"`
	MimeType       string    `json:"mime_type" validate:"required"`
	CoverImagePath string    `json:"cover_image_path"`
	Series         string    `json:"series"`
	SeriesNumber   int32     `json:"series_number"`
	Tags           []string  `json:"tags"`
	ASIN           string    `json:"asin"`
	DatePublished  string    `json:"date_published"`
	Publisher      string    `json:"publisher"`
	Contributors   []string  `json:"contributors"`
}

// UpdateMediaItemRequest represents the request for updating a media item
type UpdateMediaItemRequest struct {
	Title          string   `json:"title" validate:"required,min=1,max=500"`
	Author         string   `json:"author"`
	ISBN           string   `json:"isbn"`
	Description    string   `json:"description"`
	CoverImagePath string   `json:"cover_image_path"`
	Series         string   `json:"series"`
	SeriesNumber   int32    `json:"series_number"`
	Tags           []string `json:"tags"`
	ASIN           string   `json:"asin"`
	DatePublished  string   `json:"date_published"`
	Publisher      string   `json:"publisher"`
	Contributors   []string `json:"contributors"`
}

// CreateMediaNoteRequest represents the request for creating a media note
type CreateMediaNoteRequest struct {
	Content  string `json:"content" validate:"required,min=1,max=10000"`
	Position string `json:"position"`
}

// UpdateMediaNoteRequest represents the request for updating a media note
type UpdateMediaNoteRequest struct {
	Content  string `json:"content" validate:"required,min=1,max=10000"`
	Position string `json:"position"`
}

// CreateMediaHighlightRequest represents the request for creating a media highlight
type CreateMediaHighlightRequest struct {
	SelectionText string `json:"selection_text" validate:"required,min=1,max=5000"`
	StartPosition string `json:"start_position" validate:"required,max=100"`
	EndPosition   string `json:"end_position" validate:"required,max=100"`
	Color         string `json:"color" validate:"omitempty,len=7"`
	NoteID        string `json:"note_id"`
}

// UpdateMediaHighlightRequest represents the request for updating a media highlight
type UpdateMediaHighlightRequest struct {
	SelectionText string `json:"selection_text" validate:"required,min=1,max=5000"`
	StartPosition string `json:"start_position" validate:"required,max=100"`
	EndPosition   string `json:"end_position" validate:"required,max=100"`
	Color         string `json:"color" validate:"omitempty,len=7"`
	NoteID        string `json:"note_id"`
}

type MediaHandler struct {
	db             *database.Queries
	worker         *services.Worker
	libraryService *services.LibraryService
}

func NewMediaHandler(db *database.Queries, libraryService *services.LibraryService, worker ...*services.Worker) *MediaHandler {
	mh := &MediaHandler{
		db:             db,
		libraryService: libraryService,
	}
	if len(worker) > 0 && worker[0] != nil {
		mh.worker = worker[0]
	}
	return mh
}

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

	pgBookUUID := pgtype.UUID{Bytes: bookUUID, Valid: true}

	mediaItem, err := h.db.GetMediaItem(c.Request().Context(), pgBookUUID)
	if err != nil {
		return c.JSON(http.StatusNotFound, map[string]string{"error": "book not found"})
	}

	// Resolve relative path to absolute filesystem path
	fullPath, err := h.getFullFilePath(c.Request().Context(), mediaItem.LibraryID, mediaItem.FilePath)
	if err != nil {
		return c.JSON(http.StatusNotFound, map[string]string{"error": "book file not found on disk"})
	}

	if _, err := os.Stat(fullPath); os.IsNotExist(err) {
		return c.JSON(http.StatusNotFound, map[string]string{"error": "book file not found on disk"})
	}

	file, err := os.Open(fullPath)
	if err != nil {
		return c.JSON(http.StatusInternalServerError, map[string]string{"error": "failed to open book file"})
	}
	defer file.Close()

	mimeType := mediaItem.MimeType.String
	if !mediaItem.MimeType.Valid || mimeType == "" {
		mimeType = mime.TypeByExtension(filepath.Ext(mediaItem.FilePath))
	}

	c.Response().Header().Set("Content-Type", mimeType)
	c.Response().Header().Set("Content-Disposition", "attachment; filename=\""+filepath.Base(mediaItem.FilePath)+"\"")

	if mediaItem.FileSize.Valid && mediaItem.FileSize.Int64 > 0 {
		c.Response().Header().Set("Content-Length", strconv.FormatInt(mediaItem.FileSize.Int64, 10))
	}

	_, err = io.Copy(c.Response(), file)
	if err != nil {
		return c.JSON(http.StatusInternalServerError, map[string]string{"error": "failed to stream file"})
	}

	return nil
}

type AddToShelfRequest struct {
	MediaItemIDs  []string `json:"media_item_ids" validate:"required"`
	ShelfName     string   `json:"shelf_name"`
	ShelfPosition int      `json:"shelf_position"`
}

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

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

	if req.ShelfName == "" {
		req.ShelfName = "Default"
	}

	pgDeviceUUID := pgtype.UUID{Bytes: deviceUUID, Valid: true}
	addedCount := 0

	for _, mediaID := range req.MediaItemIDs {
		mediaUUID, err := uuid.Parse(mediaID)
		if err != nil {
			continue
		}

		_, err = h.db.AddBookToKoboShelf(c.Request().Context(), database.AddBookToKoboShelfParams{
			DeviceID:      pgDeviceUUID,
			MediaItemID:   pgtype.UUID{Bytes: mediaUUID, Valid: true},
			ShelfName:     pgtype.Text{String: req.ShelfName, Valid: true},
			ShelfPosition: pgtype.Int4{Int32: int32(req.ShelfPosition), Valid: true},
		})

		if err == nil {
			addedCount++
			req.ShelfPosition++
		}
	}

	return c.JSON(http.StatusOK, map[string]interface{}{
		"status":      "success",
		"added_count": addedCount,
		"shelf_name":  req.ShelfName,
		"total_count": len(req.MediaItemIDs),
	})
}

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

	shelfName := c.QueryParam("shelf")
	pgDeviceUUID := pgtype.UUID{Bytes: deviceUUID, Valid: true}

	var shelfBooks []database.GetKoboShelfBooksByShelfNameRow
	if shelfName != "" {
		shelfBooks, err = h.db.GetKoboShelfBooksByShelfName(c.Request().Context(), database.GetKoboShelfBooksByShelfNameParams{
			DeviceID:  pgDeviceUUID,
			ShelfName: pgtype.Text{String: shelfName, Valid: true},
		})
	} else {
		var defaultBooks []database.GetKoboShelfBooksRow
		defaultBooks, err = h.db.GetKoboShelfBooks(c.Request().Context(), pgDeviceUUID)
		shelfBooks = make([]database.GetKoboShelfBooksByShelfNameRow, len(defaultBooks))
		for i, b := range defaultBooks {
			shelfBooks[i] = database.GetKoboShelfBooksByShelfNameRow{
				ID:             b.ID,
				DeviceID:       b.DeviceID,
				MediaItemID:    b.MediaItemID,
				ShelfName:      b.ShelfName,
				ShelfPosition:  b.ShelfPosition,
				AddedAt:        b.AddedAt,
				LastSyncedAt:   b.LastSyncedAt,
				Title:          b.Title,
				Author:         b.Author,
				MimeType:       b.MimeType,
				EntitlementID:  b.EntitlementID,
				KoboContentID:  b.KoboContentID,
				RevisionNumber: b.RevisionNumber,
			}
		}
	}

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

	type ShelfBookResponse struct {
		ID             string `json:"id"`
		MediaItemID    string `json:"media_item_id"`
		Title          string `json:"title"`
		Author         string `json:"author"`
		FilePath       string `json:"file_path"`
		MimeType       string `json:"mime_type"`
		ShelfName      string `json:"shelf_name"`
		ShelfPosition  int    `json:"shelf_position"`
		EntitlementID  string `json:"entitlement_id,omitempty"`
		KoboContentID  string `json:"kobo_content_id,omitempty"`
		RevisionNumber int    `json:"revision_number"`
		AddedAt        string `json:"added_at"`
	}

	response := []ShelfBookResponse{}
	for _, book := range shelfBooks {
		response = append(response, ShelfBookResponse{
			ID:             uuid.UUID(book.ID.Bytes).String(),
			MediaItemID:    uuid.UUID(book.MediaItemID.Bytes).String(),
			Title:          book.Title,
			Author:         book.Author.String,
			MimeType:       book.MimeType.String,
			ShelfName:      book.ShelfName.String,
			ShelfPosition:  int(book.ShelfPosition.Int32),
			EntitlementID:  book.EntitlementID.String,
			KoboContentID:  book.KoboContentID.String,
			RevisionNumber: int(book.RevisionNumber.Int32),
			AddedAt:        book.AddedAt.Time.Format("2006-01-02T15:04:05Z07:00"),
		})
	}

	return c.JSON(http.StatusOK, map[string]interface{}{
		"status":     "success",
		"device_id":  deviceUUID.String(),
		"shelf_name": shelfName,
		"books":      response,
		"total":      len(response),
	})
}

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

	mediaUUID, err := uuid.Parse(c.QueryParam("media_item_id"))
	if err != nil {
		return c.JSON(http.StatusBadRequest, map[string]string{"error": "invalid media item ID"})
	}

	pgDeviceUUID := pgtype.UUID{Bytes: deviceUUID, Valid: true}
	pgMediaUUID := pgtype.UUID{Bytes: mediaUUID, Valid: true}

	err = h.db.RemoveBookFromKoboShelf(c.Request().Context(), database.RemoveBookFromKoboShelfParams{
		DeviceID:    pgDeviceUUID,
		MediaItemID: pgMediaUUID,
	})

	if err != nil {
		return c.JSON(http.StatusInternalServerError, map[string]string{"error": "failed to remove from shelf"})
	}

	return c.JSON(http.StatusOK, map[string]interface{}{
		"status":  "success",
		"message": "book removed from shelf",
	})
}

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

	shelfName := c.QueryParam("shelf")
	pgDeviceUUID := pgtype.UUID{Bytes: deviceUUID, Valid: true}

	var result string
	if shelfName != "" {
		err = h.db.ClearKoboShelfByName(c.Request().Context(), database.ClearKoboShelfByNameParams{
			DeviceID:  pgDeviceUUID,
			ShelfName: pgtype.Text{String: shelfName, Valid: true},
		})
		result = "shelf cleared"
	} else {
		err = h.db.ClearKoboShelf(c.Request().Context(), pgDeviceUUID)
		result = "all shelves cleared"
	}

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

	return c.JSON(http.StatusOK, map[string]interface{}{
		"status":  "success",
		"message": result,
	})
}

// Bulk Operations

// POST /api/media-items/bulk-delete
// Bulk delete media items
func (h *MediaHandler) HandleBulkDelete(c *echo.Context) error {
	var req struct {
		MediaItemIDs []string `json:"media_item_ids" validate:"required"`
	}

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

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

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

	for _, mediaIDStr := range req.MediaItemIDs {
		mediaID, err := uuid.Parse(mediaIDStr)
		if err != nil {
			results = append(results, map[string]interface{}{
				"media_item_id": mediaIDStr,
				"status":        "error",
				"error":         "Invalid UUID",
			})
			failedCount++
			continue
		}

		mediaUUID := pgtype.UUID{Bytes: mediaID, Valid: true}

		media, err := h.db.GetMediaItem(c.Request().Context(), mediaUUID)
		if err != nil {
			results = append(results, map[string]interface{}{
				"media_item_id": mediaIDStr,
				"status":        "error",
				"error":         "Media item not found",
			})
			failedCount++
			continue
		}

		err = h.db.DeleteMediaItem(c.Request().Context(), mediaUUID)
		if err != nil {
			results = append(results, map[string]interface{}{
				"media_item_id": mediaIDStr,
				"status":        "error",
				"error":         err.Error(),
			})
			failedCount++
			continue
		}

		if media.FilePath != "" {
			os.Remove(media.FilePath)
		}

		results = append(results, map[string]interface{}{
			"media_item_id": mediaIDStr,
			"status":        "success",
		})
		successCount++
	}

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

// POST /api/media-items/bulk-update
// Bulk update media item metadata
func (h *MediaHandler) HandleBulkUpdate(c *echo.Context) error {
	var req struct {
		MediaItemUpdates []struct {
			MediaItemID string `json:"media_item_id" validate:"required"`
			Updates     struct {
				Title    *string  `json:"title,omitempty"`
				Author   *string  `json:"author,omitempty"`
				Genre    *string  `json:"genre,omitempty"`
				Language *string  `json:"language,omitempty"`
				Tags     []string `json:"tags,omitempty"`
			} `json:"updates"`
		} `json:"media_item_updates" validate:"required"`
	}

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

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

	for _, update := range req.MediaItemUpdates {
		if update.MediaItemID == "" {
			return c.JSON(http.StatusBadRequest, map[string]string{"error": "media_item_id cannot be empty"})
		}
	}

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

	for _, update := range req.MediaItemUpdates {
		mediaID, err := uuid.Parse(update.MediaItemID)
		if err != nil {
			results = append(results, map[string]interface{}{
				"media_item_id": update.MediaItemID,
				"status":        "error",
				"error":         "Invalid UUID",
			})
			failedCount++
			continue
		}

		mediaUUID := pgtype.UUID{Bytes: mediaID, Valid: true}

		existingMedia, err := h.db.GetMediaItem(c.Request().Context(), mediaUUID)
		if err != nil {
			results = append(results, map[string]interface{}{
				"media_item_id": update.MediaItemID,
				"status":        "error",
				"error":         "Media item not found",
			})
			failedCount++
			continue
		}

		updateParams := database.UpdateMediaItemParams{
			ID:                 mediaUUID,
			Title:              existingMedia.Title,
			Author:             existingMedia.Author,
			Genre:              existingMedia.Genre,
			Language:           existingMedia.Language,
			Tags:               existingMedia.Tags,
			TagsSearch:         existingMedia.TagsSearch,
			Description:        existingMedia.Description,
			Publisher:          existingMedia.Publisher,
			CopyrightYear:      existingMedia.CopyrightYear,
			Isbn:               existingMedia.Isbn,
			Series:             existingMedia.Series,
			SeriesNumber:       existingMedia.SeriesNumber,
			Asin:               existingMedia.Asin,
			DatePublished:      existingMedia.DatePublished,
			Contributors:       existingMedia.Contributors,
			ContributorsSearch: existingMedia.ContributorsSearch,
			Edition:            existingMedia.Edition,
			PageCount:          existingMedia.PageCount,
			GoodreadsID:        existingMedia.GoodreadsID,
			OpenlibraryID:      existingMedia.OpenlibraryID,
			CoverImagePath:     existingMedia.CoverImagePath,
		}

		if update.Updates.Title != nil {
			updateParams.Title = *update.Updates.Title
		}
		if update.Updates.Author != nil {
			updateParams.Author = pgtype.Text{String: *update.Updates.Author, Valid: true}
		}
		if update.Updates.Genre != nil {
			updateParams.Genre = pgtype.Text{String: *update.Updates.Genre, Valid: true}
		}
		if update.Updates.Language != nil {
			updateParams.Language = pgtype.Text{String: *update.Updates.Language, Valid: true}
		}
		if len(update.Updates.Tags) > 0 {
			normalizedTags := utils.NormalizeTags(update.Updates.Tags)
			updateParams.Tags = normalizedTags
			tagsSearch := utils.NormalizeTagsSearch(update.Updates.Tags)
			updateParams.TagsSearch = tagsSearch
		}

		_, err = h.db.UpdateMediaItem(c.Request().Context(), updateParams)
		if err != nil {
			results = append(results, map[string]interface{}{
				"media_item_id": update.MediaItemID,
				"status":        "error",
				"error":         err.Error(),
			})
			failedCount++
			continue
		}

		results = append(results, map[string]interface{}{
			"media_item_id": update.MediaItemID,
			"status":        "success",
		})
		successCount++
	}

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

// ListMediaItems handles GET /api/media-items
func (mh *MediaHandler) ListMediaItems(c *echo.Context) error {
	libraryID := c.QueryParam("library_id")
	sort := c.QueryParam("sort")
	limit, _ := strconv.Atoi(c.QueryParam("limit"))
	offset, _ := strconv.Atoi(c.QueryParam("offset"))

	if limit == 0 {
		limit = 50
	}

	if sort == "" {
		sort = "created_at DESC"
	}

	allowedSorts := map[string]bool{
		"created_at ASC":      true,
		"created_at DESC":     true,
		"title ASC":           true,
		"title DESC":          true,
		"author ASC":          true,
		"author DESC":         true,
		"series ASC":          true,
		"series DESC":         true,
		"date_published ASC":  true,
		"date_published DESC": true,
		"copyright_year ASC":  true,
		"copyright_year DESC": true,
		"page_count ASC":      true,
		"page_count DESC":     true,
		"genre ASC":           true,
		"genre DESC":          true,
	}

	if !allowedSorts[sort] {
		sort = "created_at DESC"
	}

	if limit > maxPaginationLimit {
		limit = maxPaginationLimit
	}

	if offset < 0 {
		return c.JSON(http.StatusBadRequest, map[string]string{"error": "offset cannot be negative"})
	}

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

		items, err := mh.db.ListMediaItemsSorted(c.Request().Context(), database.ListMediaItemsSortedParams{
			LibraryID: pgtype.UUID{Bytes: libUUID, Valid: true},
			Sort:      pgtype.Text{String: sort, Valid: true},
			Limit:     pgtype.Int4{Int32: int32(limit), Valid: true},
			Offset:    pgtype.Int4{Int32: int32(offset), Valid: true},
		})
		if err != nil {
			return c.JSON(http.StatusInternalServerError, map[string]string{"error": err.Error()})
		}

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

	items, err := mh.db.ListMediaItems(c.Request().Context(), database.ListMediaItemsParams{
		Limit:  int32(limit),
		Offset: int32(offset),
	})
	if err != nil {
		return c.JSON(http.StatusInternalServerError, map[string]string{"error": err.Error()})
	}

	resolvedItems := make([]map[string]interface{}, len(items))
	for i, item := range items {
		resolvedItems[i] = map[string]interface{}{
			"id":               uuid.UUID(item.ID.Bytes).String(),
			"library_id":       uuid.UUID(item.LibraryID.Bytes).String(),
			"title":            item.Title,
			"author":           textToString(item.Author),
			"isbn":             textToString(item.Isbn),
			"description":      textToString(item.Description),
			"file_path":        utils.ResolveMediaURL(item.LibraryID, pgtype.Text{String: item.FilePath, Valid: item.FilePath != ""}),
			"file_size":        item.FileSize,
			"mime_type":        textToString(item.MimeType),
			"cover_image_path": utils.ResolveMediaURL(item.LibraryID, item.CoverImagePath),
			"series":           textToString(item.Series),
			"series_number":    item.SeriesNumber,
			"tags":             item.Tags,
			"asin":             textToString(item.Asin),
			"date_published":   item.DatePublished.Time.Format("2006-01-02"),
			"publisher":        textToString(item.Publisher),
			"contributors":     item.Contributors,
			"language":         textToString(item.Language),
			"edition":          textToString(item.Edition),
			"page_count":       item.PageCount,
			"genre":            textToString(item.Genre),
			"created_at":       item.CreatedAt.Time.Format("2006-01-02T15:04:05Z07:00"),
			"updated_at":       item.UpdatedAt.Time.Format("2006-01-02T15:04:05Z07:00"),
			"format_group":     item.FormatGroup, // already string
		}
	}
	return c.JSON(http.StatusOK, map[string]interface{}{"data": resolvedItems})
}

// GetMediaItem handles GET /api/media-items/:id
func (mh *MediaHandler) GetMediaItem(c *echo.Context) error {
	mediaID := c.Param("id")
	mediaUUID, err := uuid.Parse(mediaID)
	if err != nil {
		return c.JSON(http.StatusBadRequest, map[string]string{"error": "invalid media item id"})
	}

	item, err := mh.db.GetMediaItem(c.Request().Context(), pgtype.UUID{Bytes: mediaUUID, Valid: true})
	if err != nil {
		if err == pgx.ErrNoRows {
			return c.JSON(http.StatusNotFound, map[string]string{"error": "media item not found"})
		}
		return c.JSON(http.StatusInternalServerError, map[string]string{"error": err.Error()})
	}

	return c.JSON(http.StatusOK, map[string]interface{}{
		"id":               uuid.UUID(item.ID.Bytes).String(),
		"library_id":       uuid.UUID(item.LibraryID.Bytes).String(),
		"title":            item.Title,
		"author":           textToString(item.Author),
		"isbn":             textToString(item.Isbn),
		"description":      textToString(item.Description),
		"file_path":        utils.ResolveMediaURL(item.LibraryID, pgtype.Text{String: item.FilePath, Valid: item.FilePath != ""}),
		"file_size":        item.FileSize,
		"mime_type":        textToString(item.MimeType),
		"cover_image_path": utils.ResolveMediaURL(item.LibraryID, item.CoverImagePath),
		"series":           textToString(item.Series),
		"series_number":    item.SeriesNumber,
		"tags":             item.Tags,
		"asin":             textToString(item.Asin),
		"date_published":   item.DatePublished.Time.Format("2006-01-02"),
		"publisher":        textToString(item.Publisher),
		"contributors":     item.Contributors,
		"language":         textToString(item.Language),
		"edition":          textToString(item.Edition),
		"page_count":       item.PageCount,
		"genre":            textToString(item.Genre),
		"created_at":       item.CreatedAt.Time.Format("2006-01-02T15:04:05Z07:00"),
		"updated_at":       item.UpdatedAt.Time.Format("2006-01-02T15:04:05Z07:00"),
		"format_group":     item.FormatGroup,
		"format_mimetype":  textToString(item.FormatMimetype),
	})
}

// ListMediaItemsFiltered handles GET /api/media-items/filtered
func (mh *MediaHandler) ListMediaItemsFiltered(c *echo.Context) error {
	userID := c.Get("user_id").(string)
	libraryID := c.QueryParam("library_id")
	sort := c.QueryParam("sort")
	limit, _ := strconv.Atoi(c.QueryParam("limit"))
	offset, _ := strconv.Atoi(c.QueryParam("offset"))

	authorFilter := c.QueryParam("author_filter")
	seriesFilter := c.QueryParam("series_filter")
	genreFilter := c.QueryParam("genre_filter")
	languageFilter := c.QueryParam("language_filter")
	yearMin, _ := strconv.Atoi(c.QueryParam("year_min"))
	yearMax, _ := strconv.Atoi(c.QueryParam("year_max"))
	hasCover, _ := strconv.ParseBool(c.QueryParam("has_cover"))

	if limit == 0 {
		limit = 50
	}

	if offset < 0 {
		offset = 0
	}

	if sort == "" {
		sort = "created_at DESC"
	}

	if limit > maxPaginationLimit {
		limit = maxPaginationLimit
	}

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

	userUUID, err := uuid.Parse(userID)
	if err != nil {
		return c.JSON(http.StatusBadRequest, map[string]string{"error": "invalid user id"})
	}

	items, err := mh.db.ListMediaItemsFiltered(c.Request().Context(), database.ListMediaItemsFilteredParams{
		LibraryID:      pgtype.UUID{Bytes: libUUID, Valid: true},
		UserID:         pgtype.UUID{Bytes: userUUID, Valid: true},
		AuthorFilter:   pgtype.Text{String: authorFilter, Valid: true},
		SeriesFilter:   pgtype.Text{String: seriesFilter, Valid: true},
		GenreFilter:    pgtype.Text{String: genreFilter, Valid: true},
		LanguageFilter: pgtype.Text{String: languageFilter, Valid: true},
		YearMin:        pgtype.Int4{Int32: int32(yearMin), Valid: true},
		YearMax:        pgtype.Int4{Int32: int32(yearMax), Valid: true},
		HasCover:       pgtype.Bool{Bool: hasCover, Valid: true},
		Sort:           pgtype.Text{String: sort, Valid: true},
		Limit:          pgtype.Int4{Int32: int32(limit), Valid: true},
		Offset:         pgtype.Int4{Int32: int32(offset), Valid: true},
	})
	if err != nil {
		return c.JSON(http.StatusInternalServerError, map[string]string{"error": err.Error()})
	}

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

// CreateMediaRating handles POST /api/media-items/:id/rating
func (mh *MediaHandler) CreateMediaRating(c *echo.Context) error {
	userID := c.Get("user_id").(string)
	userUUID, err := uuid.Parse(userID)
	if err != nil {
		return c.JSON(http.StatusBadRequest, map[string]string{"error": "invalid user"})
	}

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

	var req struct {
		Rating int32 `json:"rating" validate:"required,min=1,max=10"`
	}
	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()})
	}

	rating, err := mh.db.CreateMediaRating(c.Request().Context(), database.CreateMediaRatingParams{
		MediaItemID: pgtype.UUID{Bytes: mediaUUID, Valid: true},
		UserID:      pgtype.UUID{Bytes: userUUID, Valid: true},
		Rating:      req.Rating,
	})
	if err != nil {
		return c.JSON(http.StatusInternalServerError, map[string]string{"error": err.Error()})
	}

	return c.JSON(http.StatusCreated, rating)
}

// GetMediaRating handles GET /api/media-items/:id/rating
func (mh *MediaHandler) GetMediaRating(c *echo.Context) error {
	userID := c.Get("user_id").(string)
	userUUID, err := uuid.Parse(userID)
	if err != nil {
		return c.JSON(http.StatusBadRequest, map[string]string{"error": "invalid user"})
	}

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

	rating, err := mh.db.GetMediaRating(c.Request().Context(), database.GetMediaRatingParams{
		MediaItemID: pgtype.UUID{Bytes: mediaUUID, Valid: true},
		UserID:      pgtype.UUID{Bytes: userUUID, Valid: true},
	})
	if err != nil {
		if err == pgx.ErrNoRows {
			return c.JSON(http.StatusOK, map[string]interface{}{"rating": nil})
		}
		return c.JSON(http.StatusInternalServerError, map[string]string{"error": err.Error()})
	}

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

// UpdateMediaRating handles PUT /api/media-items/:id/rating
func (mh *MediaHandler) UpdateMediaRating(c *echo.Context) error {
	return mh.CreateMediaRating(c)
}

// DeleteMediaRating handles DELETE /api/media-items/:id/rating
func (mh *MediaHandler) DeleteMediaRating(c *echo.Context) error {
	userID := c.Get("user_id").(string)
	userUUID, err := uuid.Parse(userID)
	if err != nil {
		return c.JSON(http.StatusBadRequest, map[string]string{"error": "invalid user"})
	}

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

	err = mh.db.DeleteMediaRating(c.Request().Context(), database.DeleteMediaRatingParams{
		MediaItemID: pgtype.UUID{Bytes: mediaUUID, Valid: true},
		UserID:      pgtype.UUID{Bytes: userUUID, Valid: true},
	})
	if err != nil {
		return c.JSON(http.StatusInternalServerError, map[string]string{"error": err.Error()})
	}

	return c.JSON(http.StatusOK, map[string]string{"message": "rating deleted"})
}

// GetMediaReadingProgress handles GET /api/media-items/:id/progress
func (mh *MediaHandler) GetMediaReadingProgress(c *echo.Context) error {
	userID := c.Get("user_id").(string)
	userUUID, err := uuid.Parse(userID)
	if err != nil {
		return c.JSON(http.StatusBadRequest, map[string]string{"error": "invalid user"})
	}

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

	progress, err := mh.db.GetReadingProgress(c.Request().Context(), database.GetReadingProgressParams{
		MediaItemID: pgtype.UUID{Bytes: mediaUUID, Valid: true},
		UserID:      pgtype.UUID{Bytes: userUUID, Valid: true},
	})
	if err != nil {
		if err == pgx.ErrNoRows {
			return c.JSON(http.StatusOK, map[string]interface{}{
				"current_page": 0,
				"total_pages":  nil,
			})
		}
		return c.JSON(http.StatusInternalServerError, map[string]string{"error": err.Error()})
	}

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

// UpdateMediaReadingProgress handles PUT /api/media-items/:id/progress
func (mh *MediaHandler) UpdateMediaReadingProgress(c *echo.Context) error {
	userID := c.Get("user_id").(string)
	userUUID, err := uuid.Parse(userID)
	if err != nil {
		return c.JSON(http.StatusBadRequest, map[string]string{"error": "invalid user"})
	}

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

	var req struct {
		CurrentPage int32 `json:"current_page"`
		TotalPages  int32 `json:"total_pages"`
	}
	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()})
	}

	progress, err := mh.db.UpdateReadingProgress(c.Request().Context(), database.UpdateReadingProgressParams{
		MediaItemID: pgtype.UUID{Bytes: mediaUUID, Valid: true},
		UserID:      pgtype.UUID{Bytes: userUUID, Valid: true},
		CurrentPage: pgtype.Int4{Int32: req.CurrentPage, Valid: true},
		TotalPages:  pgtype.Int4{Int32: req.TotalPages, Valid: req.TotalPages > 0},
	})
	if err != nil {
		return c.JSON(http.StatusInternalServerError, map[string]string{"error": err.Error()})
	}

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

// DeleteMediaReadingProgress handles DELETE /api/media-items/:id/progress
func (mh *MediaHandler) DeleteMediaReadingProgress(c *echo.Context) error {
	userID := c.Get("user_id").(string)
	userUUID, err := uuid.Parse(userID)
	if err != nil {
		return c.JSON(http.StatusBadRequest, map[string]string{"error": "invalid user"})
	}

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

	err = mh.db.DeleteReadingProgress(c.Request().Context(), database.DeleteReadingProgressParams{
		MediaItemID: pgtype.UUID{Bytes: mediaUUID, Valid: true},
		UserID:      pgtype.UUID{Bytes: userUUID, Valid: true},
	})
	if err != nil {
		return c.JSON(http.StatusInternalServerError, map[string]string{"error": err.Error()})
	}

	return c.JSON(http.StatusOK, map[string]string{"message": "reading progress deleted"})
}

// CreateMediaItem handles POST /api/media-items (admin only)
func (mh *MediaHandler) CreateMediaItem(c *echo.Context) error {
	user := MustGetAuthenticatedUser(c)

	if user.Role != "admin" {
		return c.JSON(http.StatusForbidden, map[string]string{"error": "admin access required"})
	}

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

	if len(req.Tags) > 0 {
		req.Tags = utils.NormalizeTags(req.Tags)
	}

	if len(req.Contributors) > 0 {
		req.Contributors = utils.NormalizeContributors(req.Contributors)
	}

	tagsSearch := utils.NormalizeTagsSearch(req.Tags)
	contributorsSearch := utils.NormalizeContributorsSearch(req.Contributors)

	// Validate and normalize ISBN
	normalizedISBN, err := utils.NormalizeISBN(req.ISBN)
	if err != nil && req.ISBN != "" {
		return c.JSON(http.StatusUnprocessableEntity, map[string]string{"error": "invalid ISBN format"})
	}

	// Use normalized ISBN if valid, otherwise empty string
	isbnValue := normalizedISBN
	if err != nil {
		isbnValue = ""
	}

	_, err = mh.db.GetLibrary(c.Request().Context(), pgtype.UUID{Bytes: req.LibraryID, Valid: true})
	if err != nil {
		if 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()})
	}

	// Validate library has folders before allowing media items
	hasFolders, err := mh.libraryService.HasFolders(c.Request().Context(), pgtype.UUID{Bytes: req.LibraryID, Valid: true})
	if err != nil {
		return c.JSON(http.StatusInternalServerError, map[string]string{"error": "Failed to validate library folders"})
	}
	if !hasFolders {
		return c.JSON(http.StatusBadRequest, map[string]string{
			"error": "Cannot add media items to a library with no folders. Please add at least one folder to the library first.",
		})
	}

	mediaItemID, err := mh.db.CreateMediaItem(c.Request().Context(), database.CreateMediaItemParams{
		LibraryID:          pgtype.UUID{Bytes: req.LibraryID, Valid: true},
		Title:              req.Title,
		Author:             pgtype.Text{String: req.Author, Valid: req.Author != ""},
		Isbn:               pgtype.Text{String: isbnValue, Valid: req.ISBN != ""},
		Description:        pgtype.Text{String: req.Description, Valid: req.Description != ""},
		FilePath:           req.FilePath,
		FileSize:           pgtype.Int8{Int64: req.FileSize, Valid: req.FileSize > 0},
		MimeType:           pgtype.Text{String: req.MimeType, Valid: req.MimeType != ""},
		CoverImagePath:     pgtype.Text{String: req.CoverImagePath, Valid: req.CoverImagePath != ""},
		Series:             pgtype.Text{String: req.Series, Valid: req.Series != ""},
		SeriesNumber:       pgtype.Int4{Int32: req.SeriesNumber, Valid: req.SeriesNumber > 0},
		Tags:               req.Tags,
		TagsSearch:         tagsSearch,
		Asin:               pgtype.Text{String: req.ASIN, Valid: req.ASIN != ""},
		DatePublished:      pgtype.Date{Time: parseDate(req.DatePublished), Valid: req.DatePublished != ""},
		Publisher:          pgtype.Text{String: req.Publisher, Valid: req.Publisher != ""},
		Contributors:       req.Contributors,
		ContributorsSearch: contributorsSearch,
		AddedByAdminID:     user.ID,
	})
	if err != nil {
		return c.JSON(http.StatusInternalServerError, map[string]string{"error": err.Error()})
	}

	item, err := mh.db.GetMediaItem(c.Request().Context(), pgtype.UUID{Bytes: mediaItemID.ID.Bytes, Valid: true})
	if err != nil {
		return c.JSON(http.StatusInternalServerError, map[string]string{"error": err.Error()})
	}

	return c.JSON(http.StatusCreated, item)
}

// UpdateMediaItem handles PUT /api/media-items/:id (admin only)
func (mh *MediaHandler) UpdateMediaItem(c *echo.Context) error {
	user := MustGetAuthenticatedUser(c)

	if user.Role != "admin" {
		return c.JSON(http.StatusForbidden, map[string]string{"error": "admin access required"})
	}

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

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

	if len(req.Tags) > 0 {
		req.Tags = utils.NormalizeTags(req.Tags)
	}

	if len(req.Contributors) > 0 {
		req.Contributors = utils.NormalizeContributors(req.Contributors)
	}

	tagsSearch := utils.NormalizeTagsSearch(req.Tags)
	contributorsSearch := utils.NormalizeContributorsSearch(req.Contributors)

	// Validate and normalize ISBN
	normalizedISBN, err := utils.NormalizeISBN(req.ISBN)
	if err != nil && req.ISBN != "" {
		return c.JSON(http.StatusUnprocessableEntity, map[string]string{"error": "invalid ISBN format"})
	}

	// Use normalized ISBN if valid, otherwise empty string
	isbnValue := normalizedISBN
	if err != nil {
		isbnValue = ""
	}

	item, err := mh.db.UpdateMediaItem(c.Request().Context(), database.UpdateMediaItemParams{
		ID:                 pgtype.UUID{Bytes: mediaUUID, Valid: true},
		Title:              req.Title,
		Author:             pgtype.Text{String: req.Author, Valid: req.Author != ""},
		Isbn:               pgtype.Text{String: isbnValue, Valid: req.ISBN != ""},
		Description:        pgtype.Text{String: req.Description, Valid: req.Description != ""},
		CoverImagePath:     pgtype.Text{String: req.CoverImagePath, Valid: req.CoverImagePath != ""},
		Series:             pgtype.Text{String: req.Series, Valid: req.Series != ""},
		SeriesNumber:       pgtype.Int4{Int32: req.SeriesNumber, Valid: req.SeriesNumber > 0},
		Tags:               req.Tags,
		TagsSearch:         tagsSearch,
		Asin:               pgtype.Text{String: req.ASIN, Valid: req.ASIN != ""},
		DatePublished:      pgtype.Date{Time: parseDate(req.DatePublished), Valid: req.DatePublished != ""},
		Publisher:          pgtype.Text{String: req.Publisher, Valid: req.Publisher != ""},
		Contributors:       req.Contributors,
		ContributorsSearch: contributorsSearch,
	})
	if err != nil {
		return c.JSON(http.StatusInternalServerError, map[string]string{"error": err.Error()})
	}

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

// DeleteMediaItem handles DELETE /api/media-items/:id (admin only)
func (mh *MediaHandler) DeleteMediaItem(c *echo.Context) error {
	user := MustGetAuthenticatedUser(c)

	if user.Role != "admin" {
		return c.JSON(http.StatusForbidden, map[string]string{"error": "admin access required"})
	}

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

	err = mh.db.DeleteMediaItem(c.Request().Context(), pgtype.UUID{Bytes: mediaUUID, Valid: true})
	if err != nil {
		return c.JSON(http.StatusInternalServerError, map[string]string{"error": err.Error()})
	}

	return c.NoContent(http.StatusNoContent)
}

// GetMediaNotes handles GET /api/media-items/:id/notes
func (mh *MediaHandler) GetMediaNotes(c *echo.Context) error {
	userID := c.Get("user_id").(string)
	userUUID, err := uuid.Parse(userID)
	if err != nil {
		return c.JSON(http.StatusBadRequest, map[string]string{"error": "invalid user"})
	}

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

	notes, err := mh.db.GetMediaNotes(c.Request().Context(), database.GetMediaNotesParams{
		MediaItemID: pgtype.UUID{Bytes: mediaUUID, Valid: true},
		UserID:      pgtype.UUID{Bytes: userUUID, Valid: true},
	})
	if err != nil {
		return c.JSON(http.StatusInternalServerError, map[string]string{"error": err.Error()})
	}

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

// CreateMediaNote handles POST /api/media-items/:id/notes
func (mh *MediaHandler) CreateMediaNote(c *echo.Context) error {
	userID := c.Get("user_id").(string)
	userUUID, err := uuid.Parse(userID)
	if err != nil {
		return c.JSON(http.StatusBadRequest, map[string]string{"error": "invalid user"})
	}

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

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

	note, err := mh.db.CreateMediaNote(c.Request().Context(), database.CreateMediaNoteParams{
		MediaItemID: pgtype.UUID{Bytes: mediaUUID, Valid: true},
		UserID:      pgtype.UUID{Bytes: userUUID, Valid: true},
		Content:     req.Content,
		Position:    pgtype.Text{String: req.Position, Valid: req.Position != ""},
	})
	if err != nil {
		return c.JSON(http.StatusInternalServerError, map[string]string{"error": err.Error()})
	}

	return c.JSON(http.StatusCreated, note)
}

// GetMediaNote handles GET /api/media-items/:id/notes/:noteId
func (mh *MediaHandler) GetMediaNote(c *echo.Context) error {
	noteID := c.Param("noteId")
	noteUUID, err := uuid.Parse(noteID)
	if err != nil {
		return c.JSON(http.StatusBadRequest, map[string]string{"error": "invalid note id"})
	}

	note, err := mh.db.GetMediaNote(c.Request().Context(), pgtype.UUID{Bytes: noteUUID, Valid: true})
	if err != nil {
		if err == pgx.ErrNoRows {
			return c.JSON(http.StatusNotFound, map[string]string{"error": "note not found"})
		}
		return c.JSON(http.StatusInternalServerError, map[string]string{"error": err.Error()})
	}

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

// UpdateMediaNote handles PUT /api/media-items/:id/notes/:noteId
func (mh *MediaHandler) UpdateMediaNote(c *echo.Context) error {
	noteID := c.Param("noteId")
	noteUUID, err := uuid.Parse(noteID)
	if err != nil {
		return c.JSON(http.StatusBadRequest, map[string]string{"error": "invalid note id"})
	}

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

	note, err := mh.db.UpdateMediaNote(c.Request().Context(), database.UpdateMediaNoteParams{
		ID:       pgtype.UUID{Bytes: noteUUID, Valid: true},
		Content:  req.Content,
		Position: pgtype.Text{String: req.Position, Valid: req.Position != ""},
	})
	if err != nil {
		return c.JSON(http.StatusInternalServerError, map[string]string{"error": err.Error()})
	}

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

// DeleteMediaNote handles DELETE /api/media-items/:id/notes/:noteId
func (mh *MediaHandler) DeleteMediaNote(c *echo.Context) error {
	noteID := c.Param("noteId")
	noteUUID, err := uuid.Parse(noteID)
	if err != nil {
		return c.JSON(http.StatusBadRequest, map[string]string{"error": "invalid note id"})
	}

	err = mh.db.DeleteMediaNote(c.Request().Context(), pgtype.UUID{Bytes: noteUUID, Valid: true})
	if err != nil {
		return c.JSON(http.StatusInternalServerError, map[string]string{"error": err.Error()})
	}

	return c.NoContent(http.StatusNoContent)
}

// GetMediaHighlights handles GET /api/media-items/:id/highlights
func (mh *MediaHandler) GetMediaHighlights(c *echo.Context) error {
	userID := c.Get("user_id").(string)
	userUUID, err := uuid.Parse(userID)
	if err != nil {
		return c.JSON(http.StatusBadRequest, map[string]string{"error": "invalid user"})
	}

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

	highlights, err := mh.db.GetMediaHighlights(c.Request().Context(), database.GetMediaHighlightsParams{
		MediaItemID: pgtype.UUID{Bytes: mediaUUID, Valid: true},
		UserID:      pgtype.UUID{Bytes: userUUID, Valid: true},
	})
	if err != nil {
		return c.JSON(http.StatusInternalServerError, map[string]string{"error": err.Error()})
	}

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

// CreateMediaHighlight handles POST /api/media-items/:id/highlights
func (mh *MediaHandler) CreateMediaHighlight(c *echo.Context) error {
	userID := c.Get("user_id").(string)
	userUUID, err := uuid.Parse(userID)
	if err != nil {
		return c.JSON(http.StatusBadRequest, map[string]string{"error": "invalid user"})
	}

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

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

	var noteUUID pgtype.UUID
	if req.NoteID != "" {
		if noteID, err := uuid.Parse(req.NoteID); err != nil {
			return c.JSON(http.StatusBadRequest, map[string]string{"error": "invalid note id"})
		} else {
			noteUUID = pgtype.UUID{Bytes: noteID, Valid: true}
		}
	}

	color := "#ffff00"
	if req.Color != "" {
		color = req.Color
	}

	highlight, err := mh.db.CreateMediaHighlight(c.Request().Context(), database.CreateMediaHighlightParams{
		MediaItemID:   pgtype.UUID{Bytes: mediaUUID, Valid: true},
		UserID:        pgtype.UUID{Bytes: userUUID, Valid: true},
		SelectionText: req.SelectionText,
		StartPosition: pgtype.Text{String: req.StartPosition, Valid: true},
		EndPosition:   pgtype.Text{String: req.EndPosition, Valid: true},
		Color:         pgtype.Text{String: color, Valid: true},
		NoteID:        noteUUID,
	})
	if err != nil {
		return c.JSON(http.StatusInternalServerError, map[string]string{"error": err.Error()})
	}

	return c.JSON(http.StatusCreated, highlight)
}

// GetMediaHighlight handles GET /api/media-items/:id/highlights/:highlightId
func (mh *MediaHandler) GetMediaHighlight(c *echo.Context) error {
	highlightID := c.Param("highlightId")
	highlightUUID, err := uuid.Parse(highlightID)
	if err != nil {
		return c.JSON(http.StatusBadRequest, map[string]string{"error": "invalid highlight id"})
	}

	highlight, err := mh.db.GetMediaHighlight(c.Request().Context(), pgtype.UUID{Bytes: highlightUUID, Valid: true})
	if err != nil {
		if err == pgx.ErrNoRows {
			return c.JSON(http.StatusNotFound, map[string]string{"error": "highlight not found"})
		}
		return c.JSON(http.StatusInternalServerError, map[string]string{"error": err.Error()})
	}

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

// UpdateMediaHighlight handles PUT /api/media-items/:id/highlights/:highlightId
func (mh *MediaHandler) UpdateMediaHighlight(c *echo.Context) error {
	highlightID := c.Param("highlightId")
	highlightUUID, err := uuid.Parse(highlightID)
	if err != nil {
		return c.JSON(http.StatusBadRequest, map[string]string{"error": "invalid highlight id"})
	}

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

	var noteUUID pgtype.UUID
	if req.NoteID != "" {
		if noteID, err := uuid.Parse(req.NoteID); err != nil {
			return c.JSON(http.StatusBadRequest, map[string]string{"error": "invalid note id"})
		} else {
			noteUUID = pgtype.UUID{Bytes: noteID, Valid: true}
		}
	}

	color := "#ffff00"
	if req.Color != "" {
		color = req.Color
	}

	highlight, err := mh.db.UpdateMediaHighlight(c.Request().Context(), database.UpdateMediaHighlightParams{
		ID:            pgtype.UUID{Bytes: highlightUUID, Valid: true},
		SelectionText: req.SelectionText,
		StartPosition: pgtype.Text{String: req.StartPosition, Valid: true},
		EndPosition:   pgtype.Text{String: req.EndPosition, Valid: true},
		Color:         pgtype.Text{String: color, Valid: true},
		NoteID:        noteUUID,
	})
	if err != nil {
		return c.JSON(http.StatusInternalServerError, map[string]string{"error": err.Error()})
	}

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

// DeleteMediaHighlight handles DELETE /api/media-items/:id/highlights/:highlightId
func (mh *MediaHandler) DeleteMediaHighlight(c *echo.Context) error {
	highlightID := c.Param("highlightId")
	highlightUUID, err := uuid.Parse(highlightID)
	if err != nil {
		return c.JSON(http.StatusBadRequest, map[string]string{"error": "invalid highlight id"})
	}

	err = mh.db.DeleteMediaHighlight(c.Request().Context(), pgtype.UUID{Bytes: highlightUUID, Valid: true})
	if err != nil {
		return c.JSON(http.StatusInternalServerError, map[string]string{"error": err.Error()})
	}

	return c.NoContent(http.StatusNoContent)
}

// SearchMediaItems handles GET /api/media-items/search
func (mh *MediaHandler) SearchMediaItems(c *echo.Context) error {
	query := c.QueryParam("q")

	// Safely get user from context
	userID, ok := c.Get("user").(database.Users)
	if !ok {
		return c.JSON(http.StatusUnauthorized, map[string]string{"error": "user not authenticated"})
	}

	libraryID := c.QueryParam("library_id")

	if query == "" {
		return c.JSON(http.StatusBadRequest, map[string]string{"error": "query parameter 'q' is required"})
	}

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

	limit := int32(50)
	offset := int32(0)

	searchPattern := "%" + query + "%"

	// Build params - conditionally and library_id filter
	partialParams := database.SearchMediaItemsParams{
		SearchPattern: pgtype.Text{String: searchPattern, Valid: true},
		UserID:        userID.ID,
		Limit:         pgtype.Int4{Int32: limit, Valid: true},
		Offset:        pgtype.Int4{Int32: offset, Valid: true},
		LibraryID:     libUUID, // May be invalid (empty)
	}

	partialResults, err := mh.db.SearchMediaItems(c.Request().Context(), partialParams)

	if err != nil && err != pgx.ErrNoRows {
		c.Logger().Error("search error", "error", err.Error(), "query", query, "library_id", libraryID)
		return c.JSON(http.StatusInternalServerError, map[string]string{"error": err.Error()})
	}

	if len(partialResults) > 0 {
		return c.JSON(http.StatusOK, partialResults)
	}

	fuzzyParams := database.SearchMediaItemsFuzzyParams{
		SearchQuery: pgtype.Text{String: query, Valid: true},
		UserID:      userID.ID,
		Limit:       pgtype.Int4{Int32: limit, Valid: true},
		Offset:      pgtype.Int4{Int32: offset, Valid: true},
		LibraryID:   libUUID,
	}

	fuzzyResults, err := mh.db.SearchMediaItemsFuzzy(c.Request().Context(), fuzzyParams)

	if err != nil && err != pgx.ErrNoRows {
		c.Logger().Error("fuzzy search error", "error", err.Error(), "query", query)
		return c.JSON(http.StatusInternalServerError, map[string]string{"error": err.Error()})
	}

	if len(fuzzyResults) == 0 {
		return c.JSON(http.StatusNotFound, map[string]interface{}{
			"error":   "no results found",
			"query":   query,
			"results": []interface{}{},
		})
	}

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

// getFullFilePath returns the absolute filesystem path for a media item
// Uses LibraryService for resolution (one source of truth)
func (mh *MediaHandler) getFullFilePath(ctx context.Context, libraryID pgtype.UUID, relativePath string) (string, error) {
	if relativePath == "" {
		return "", fmt.Errorf("no file path")
	}

	// Check if already absolute (backward compatibility)
	if filepath.IsAbs(relativePath) {
		return relativePath, nil
	}

	// Use service for resolution (one source of truth)
	return mh.libraryService.ResolveMediaPath(ctx, libraryID, relativePath)
}

// ServeFile serves files (covers or books) via /uploads/library-{id}/path
// Requires JWT authentication
func (mh *MediaHandler) ServeFile(c *echo.Context) error {
	// URL format: /uploads/library-{libraryID}/{relativePath}
	// Get library ID directly from route parameter
	libraryIDStr := c.Param("id")
	libraryUUID, err := uuid.Parse(libraryIDStr)
	if err != nil {
		return c.JSON(http.StatusBadRequest, map[string]string{"error": "invalid library ID"})
	}

	// Get remaining path from URL
	rawPath := c.Param("*")
	relativePath, err := url.QueryUnescape(rawPath)
	if err != nil {
		relativePath = rawPath
	}

	if relativePath == "" {
		return c.JSON(http.StatusBadRequest, map[string]string{"error": "invalid path"})
	}

	// Resolve using service
	fullPath, err := mh.getFullFilePath(c.Request().Context(), pgtype.UUID{Bytes: libraryUUID, Valid: true}, relativePath)
	if err != nil {
		return c.JSON(http.StatusNotFound, map[string]string{"error": "file not found"})
	}

	// Check if file exists
	if _, err := os.Stat(fullPath); os.IsNotExist(err) {
		return c.JSON(http.StatusNotFound, map[string]string{"error": "file not found"})
	}

	ext := strings.ToLower(filepath.Ext(fullPath))
	contentType := services.MimeTypes[ext]
	if contentType == "" {
		contentType = "application/octet-stream"
	}

	c.Response().Header().Set("Content-Type", contentType)
	c.Response().Header().Set("Cache-Control", "public, max-age=86400")
	http.ServeFile(c.Response(), c.Request(), fullPath)
	return nil
}
