package handlers

import (
	"bookhoard/internal/database"
	wsync "bookhoard/internal/sync"
	"encoding/json"
	"fmt"
	"net/http"
	"time"

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

type KOReaderHandler struct {
	db          *database.Queries
	connManager *wsync.ConnectionManager
	queue       *wsync.SyncQueueProcessor
}

func NewKOReaderHandler(db *database.Queries, connManager *wsync.ConnectionManager, queue *wsync.SyncQueueProcessor) *KOReaderHandler {
	return &KOReaderHandler{db: db, connManager: connManager, queue: queue}
}

type KOReaderProgressRequest struct {
	LibraryID *string                `json:"library_id,omitempty"`
	Books     []KOReaderBookProgress `json:"books" validate:"required"`
	SyncMode  string                 `json:"sync_mode,omitempty"`
}

type KOReaderBookProgress struct {
	UUID       string              `json:"uuid,omitempty"`
	SHA256     string              `json:"sha256,omitempty"`
	Title      string              `json:"title,omitempty"`
	Authors    []string            `json:"authors,omitempty"`
	Progress   float64             `json:"progress"`
	Percentage float64             `json:"percentage"`
	LastRead   string              `json:"last_read,omitempty"`
	FilePath   string              `json:"file_path,omitempty"`
	DeviceInfo KOReaderDeviceInfo  `json:"device_info,omitempty"`
	Bookmarks  []KOReaderBookmark  `json:"bookmarks,omitempty"`
	Highlights []KOReaderHighlight `json:"highlights,omitempty"`
	Notes      []KOReaderNote      `json:"notes,omitempty"`
	Chapter    *int                `json:"chapter,omitempty"`
	Character  *int64              `json:"character,omitempty"`
	Epubcfi    *string             `json:"epubcfi,omitempty"`
	Page       *int                `json:"page,omitempty"`
	TotalPages *int                `json:"total_pages,omitempty"`
}

type KOReaderDeviceInfo struct {
	KOReaderVersion string `json:"koreader_version,omitempty"`
	DeviceModel     string `json:"device_model,omitempty"`
}

type KOReaderBookmark struct {
	Chapter    int      `json:"chapter,omitempty"`
	Datetime   string   `json:"datetime,omitempty"`
	Notes      string   `json:"notes,omitempty"`
	Pos0       string   `json:"pos0,omitempty"`
	Pos1       string   `json:"pos1,omitempty"`
	Page       int      `json:"page,omitempty"`
	Text       string   `json:"text,omitempty"`
	Type       string   `json:"type,omitempty"`
	Percentage *float64 `json:"percentage,omitempty"`
	BookSHA256 string   `json:"book_sha256,omitempty"`
}

type KOReaderHighlight struct {
	Chapter    int      `json:"chapter,omitempty"`
	Datetime   string   `json:"datetime,omitempty"`
	Notes      string   `json:"notes,omitempty"`
	Pos0       string   `json:"pos0,omitempty"`
	Pos1       string   `json:"pos1,omitempty"`
	Page       int      `json:"page,omitempty"`
	Text       string   `json:"text,omitempty"`
	Type       string   `json:"type,omitempty"`
	Color      string   `json:"color,omitempty"`
	Percentage *float64 `json:"percentage,omitempty"`
	BookSHA256 string   `json:"book_sha256,omitempty"`
}

type KOReaderNote struct {
	Chapter    int      `json:"chapter,omitempty"`
	Datetime   string   `json:"datetime,omitempty"`
	Notes      string   `json:"notes,omitempty"`
	Pos0       string   `json:"pos0,omitempty"`
	Pos1       string   `json:"pos1,omitempty"`
	Page       int      `json:"page,omitempty"`
	Text       string   `json:"text,omitempty"`
	Type       string   `json:"type,omitempty"`
	Percentage *float64 `json:"percentage,omitempty"`
	BookSHA256 string   `json:"book_sha256,omitempty"`
}

type KOReaderSyncResponse struct {
	SyncStatus    string             `json:"sync_status"`
	BooksSynced   int                `json:"books_synced"`
	Conflicts     []KOReaderConflict `json:"conflicts,omitempty"`
	Timestamp     string             `json:"timestamp"`
	DeviceUpdated bool               `json:"device_updated"`
}

type KOReaderConflict struct {
	BookUUID       string  `json:"book_uuid"`
	ConflictType   string  `json:"conflict_type"`
	DeviceProgress float64 `json:"device_progress"`
	ServerProgress float64 `json:"server_progress"`
	Resolution     string  `json:"resolution"`
}

type KOReaderMetadata struct {
	UUID        string               `json:"uuid"`
	Title       string               `json:"title"`
	Authors     []string             `json:"authors"`
	Progress    KOReaderProgressData `json:"progress"`
	Annotations KOReaderAnnotations  `json:"annotations"`
	LastSync    string               `json:"last_sync"`
}

type KOReaderProgressData struct {
	Percentage      float64  `json:"percentage"`
	Character       *int64   `json:"character,omitempty"`
	Epubcfi         *string  `json:"epubcfi,omitempty"`
	Chapter         *int     `json:"chapter,omitempty"`
	ChapterProgress *float64 `json:"chapter_progress,omitempty"`
	Page            *int     `json:"page,omitempty"`
	TotalPages      *int     `json:"total_pages,omitempty"`
}

type KOReaderAnnotations struct {
	Highlights []KOReaderHighlight `json:"highlights,omitempty"`
	Notes      []KOReaderNote      `json:"notes,omitempty"`
	Bookmarks  []KOReaderBookmark  `json:"bookmarks,omitempty"`
}

type KOReaderLibraryResponse struct {
	LibrarySync []KOReaderLibraryBook `json:"library_sync"`
	TotalBooks  int                   `json:"total_books"`
	LastSync    string                `json:"last_sync"`
}

type KOReaderLibraryBook struct {
	UUID           string  `json:"uuid"`
	Title          string  `json:"title"`
	Author         string  `json:"author"`
	ContentType    string  `json:"content_type"`
	PercentRead    float64 `json:"percent_read"`
	PagesRemaining *int    `json:"pages_remaining,omitempty"`
	BookmarkCount  int     `json:"bookmark_count"`
	LastModified   string  `json:"last_modified"`
}

func (h *KOReaderHandler) SyncProgress(c *echo.Context) error {
	device := c.Get("device").(database.Devices)
	userID := device.UserID.Bytes

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

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

	pgUserID := pgtype.UUID{Bytes: userID, Valid: true}

	syncMode := req.SyncMode
	if syncMode == "" {
		syncMode = "immediate"
	}

	if syncMode == "checkpoint" {
		return h.handleCheckpointSync(c, device, pgUserID, req)
	}

	booksSynced := 0
	conflicts := []KOReaderConflict{}

	for _, book := range req.Books {
		mediaItemID, _ := h.resolveBookToMediaItem(c, device.ID, pgUserID, book)
		if !mediaItemID.Valid {
			continue
		}

		err := h.updateProgressForBook(c, pgUserID, mediaItemID, book)
		if err == nil {
			booksSynced++
		}
	}

	_, err := h.db.UpdateDeviceLastSync(c.Request().Context(), device.ID)
	if err != nil {
		return c.JSON(http.StatusInternalServerError, map[string]string{
			"error": "failed to update device timestamp",
		})
	}

	if syncMode == "immediate" {
		return c.JSON(http.StatusAccepted, KOReaderSyncResponse{
			SyncStatus:    "accepted",
			BooksSynced:   booksSynced,
			Conflicts:     conflicts,
			Timestamp:     time.Now().Format(time.RFC3339),
			DeviceUpdated: true,
		})
	}

	return c.JSON(http.StatusOK, KOReaderSyncResponse{
		SyncStatus:    "completed",
		BooksSynced:   booksSynced,
		Conflicts:     conflicts,
		Timestamp:     time.Now().Format(time.RFC3339),
		DeviceUpdated: true,
	})
}

func (h *KOReaderHandler) resolveBookToMediaItem(c *echo.Context, deviceID pgtype.UUID, userID pgtype.UUID, book KOReaderBookProgress) (pgtype.UUID, float64) {
	ctx := c.Request().Context()

	// Priority 1: UUID provided (highest confidence - 1.0)
	if book.UUID != "" {
		mediaUUID, err := uuid.Parse(book.UUID)
		if err == nil {
			mediaItem, err := h.db.GetMediaItem(ctx, pgtype.UUID{Bytes: mediaUUID, Valid: true})
			if err == nil {
				// Create device file alias if FilePath is also provided
				if book.FilePath != "" {
					h.createDeviceFileAlias(c, deviceID, mediaItem.ID, book)
				}
				return mediaItem.ID, 1.0
			}
		}
	}

	// Priority 2: SHA-256 provided (medium confidence - 0.9)
	if book.SHA256 != "" && len(book.SHA256) == 64 {
		mediaItem, err := h.db.GetMediaItemBySHA256(ctx, pgtype.Text{String: book.SHA256, Valid: true})
		if err == nil {
			// Create device file alias if FilePath is provided
			if book.FilePath != "" {
				h.createDeviceFileAlias(c, deviceID, mediaItem.ID, book)
			}
			return mediaItem.ID, 0.9
		}
	}

	// Priority 3: FilePath provided (check existing alias or create new - confidence 0.7)
	if book.FilePath != "" {
		// Check if alias already exists
		alias, err := h.db.GetDeviceFileAlias(ctx, database.GetDeviceFileAliasParams{
			DeviceID: deviceID,
			FilePath: book.FilePath,
		})
		if err == nil {
			return alias.MediaItemID, alias.ConfidenceScore.Float64
		}

		// Try to find by file path (search any library)
		mediaItem, err := h.db.GetMediaItemByFilePathAnyLibrary(ctx, book.FilePath)
		if err == nil {
			// Create new alias
			confidence := 0.7
			h.createDeviceFileAlias(c, deviceID, mediaItem.ID, book)
			return mediaItem.ID, confidence
		}
	}

	// Priority 4: Title + Author match (lowest confidence - 0.5)
	if book.Title != "" {
		mediaItems, err := h.db.ListMediaItems(ctx, database.ListMediaItemsParams{
			Limit:  100,
			Offset: 0,
		})
		if err == nil {
			for _, mi := range mediaItems {
				if mi.Title == book.Title {
					// Check author match if provided
					if len(book.Authors) > 0 && mi.Author.Valid {
						if mi.Author.String == book.Authors[0] {
							// Create device file alias if FilePath is provided
							if book.FilePath != "" {
								h.createDeviceFileAlias(c, deviceID, mi.ID, book)
							}
							return mi.ID, 0.5
						}
					} else {
						// Title only match
						if book.FilePath != "" {
							h.createDeviceFileAlias(c, deviceID, mi.ID, book)
						}
						return mi.ID, 0.4
					}
				}
			}
		}
	}

	return pgtype.UUID{}, 0.0
}

func (h *KOReaderHandler) createDeviceFileAlias(c *echo.Context, deviceID pgtype.UUID, mediaItemID pgtype.UUID, book KOReaderBookProgress) {
	ctx := c.Request().Context()

	if book.FilePath == "" {
		return
	}

	sha256 := book.SHA256
	if sha256 == "" {
		// Try to get SHA256 from media item
		mediaItem, err := h.db.GetMediaItem(ctx, mediaItemID)
		if err == nil && mediaItem.FileSha256.Valid {
			sha256 = mediaItem.FileSha256.String
		}
	}

	confidence := 0.7
	// Check if alias already exists
	_, err := h.db.GetDeviceFileAlias(ctx, database.GetDeviceFileAliasParams{
		DeviceID: deviceID,
		FilePath: book.FilePath,
	})
	if err != nil {
		// Create new alias
		_, _ = h.db.CreateDeviceFileAlias(ctx, database.CreateDeviceFileAliasParams{
			MediaItemID:     mediaItemID,
			DeviceID:        deviceID,
			FilePath:        book.FilePath,
			FileSha256:      pgtype.Text{String: sha256, Valid: sha256 != ""},
			ConfidenceScore: pgtype.Float8{Float64: confidence, Valid: true},
		})
	}
}

func (h *KOReaderHandler) handleCheckpointSync(c *echo.Context, device database.Devices, userID pgtype.UUID, req KOReaderProgressRequest) error {
	booksEnqueued := 0

	for _, book := range req.Books {
		mediaItemID, _ := h.resolveBookToMediaItem(c, device.ID, userID, book)
		if !mediaItemID.Valid {
			continue
		}

		err := h.enqueueProgressForBook(c, device.ID, userID, mediaItemID, book)
		if err == nil {
			booksEnqueued++
		}
	}

	_, err := h.db.UpdateDeviceLastSync(c.Request().Context(), device.ID)
	if err != nil {
		return c.JSON(http.StatusInternalServerError, map[string]string{
			"error": "failed to update device timestamp",
		})
	}

	return c.JSON(http.StatusAccepted, map[string]interface{}{
		"sync_status":    "checkpoint_enqueued",
		"books_enqueued": booksEnqueued,
		"message":        "Sync will be processed in the background",
		"timestamp":      time.Now().Format(time.RFC3339),
	})
}

func (h *KOReaderHandler) enqueueProgressForBook(c *echo.Context, deviceID pgtype.UUID, userID pgtype.UUID, mediaItemID pgtype.UUID, book KOReaderBookProgress) error {
	if h.queue == nil {
		return fmt.Errorf("sync queue not available")
	}

	update := &wsync.ProgressUpdate{
		DeviceID:    deviceID,
		MediaItemID: mediaItemID,
		UserID:      userID,
		Percentage:  book.Percentage,
		Epubcfi:     book.Epubcfi,
		Chapter:     book.Chapter,
		Character:   book.Character,
		Page:        book.Page,
		TotalPages:  book.TotalPages,
		Source:      "koreader",
		SyncMode:    "checkpoint",
	}

	return h.queue.EnqueueProgress(update)
}

func (h *KOReaderHandler) updateProgressForBook(c *echo.Context, userID pgtype.UUID, mediaItemID pgtype.UUID, book KOReaderBookProgress) error {
	ctx := c.Request().Context()

	existingProgress, err := h.db.GetReadingProgress(ctx, database.GetReadingProgressParams{
		MediaItemID: mediaItemID,
		UserID:      userID,
	})

	if err != nil && err != pgx.ErrNoRows {
		return err
	}

	hasExistingProgress := err != pgx.ErrNoRows
	conflictDetected := false

	if hasExistingProgress && existingProgress.LastSyncSource.Valid {
		if existingProgress.LastSyncSource.String != "koreader" && existingProgress.LastSyncTimestamp.Valid {
			timeDiff := time.Since(existingProgress.LastSyncTimestamp.Time)
			if timeDiff < 5*time.Minute {
				percentageDiff := book.Percentage - existingProgress.Percentage.Float64
				if percentageDiff < 0 {
					percentageDiff = -percentageDiff
				}
				if percentageDiff > 0.01 {
					conflictDetected = true
				}
			}
		}
	}

	var epubcfi pgtype.Text
	var chapter pgtype.Int4
	var characterOffset pgtype.Int8
	var currentPage pgtype.Int4
	var totalPages pgtype.Int4

	if book.Epubcfi != nil {
		epubcfi = pgtype.Text{String: *book.Epubcfi, Valid: true}
	}
	if book.Chapter != nil {
		chapter = pgtype.Int4{Int32: int32(*book.Chapter), Valid: true}
	}
	if book.Character != nil {
		characterOffset = pgtype.Int8{Int64: *book.Character, Valid: true}
	}
	if book.Page != nil {
		currentPage = pgtype.Int4{Int32: int32(*book.Page), Valid: true}
	}
	if book.TotalPages != nil {
		totalPages = pgtype.Int4{Int32: int32(*book.TotalPages), Valid: true}
	}

	_, err = h.db.UpdateUniversalProgress(ctx, database.UpdateUniversalProgressParams{
		MediaItemID:     mediaItemID,
		UserID:          userID,
		Percentage:      pgtype.Float8{Float64: book.Percentage, Valid: true},
		Epubcfi:         epubcfi,
		Chapter:         chapter,
		ChapterProgress: pgtype.Float8{Float64: book.Percentage, Valid: true},
		CharacterOffset: characterOffset,
		CurrentPage:     currentPage,
		TotalPages:      totalPages,
		LastSyncDevice:  pgtype.Text{String: "koreader", Valid: true},
		LastSyncSource:  pgtype.Text{String: "koreader", Valid: true},
		ViewportY:       pgtype.Float8{},
		ScrollPositionX: pgtype.Float8{},
		ScrollPositionY: pgtype.Float8{},
		PanelNumber:     pgtype.Int4{},
		ReadingMode:     pgtype.Text{},
		ZoomLevel:       pgtype.Float8{},
	})

	if err != nil {
		return err
	}

	if conflictDetected {
		koreaderData := map[string]interface{}{
			"source":    "koreader",
			"timestamp": time.Now(),
			"data": map[string]interface{}{
				"percentage": book.Percentage,
			},
		}
		if book.Epubcfi != nil {
			koreaderData["data"].(map[string]interface{})["epubcfi"] = *book.Epubcfi
		}
		if book.Chapter != nil {
			koreaderData["data"].(map[string]interface{})["chapter"] = *book.Chapter
		}
		if book.Character != nil {
			koreaderData["data"].(map[string]interface{})["character"] = *book.Character
		}
		if book.Page != nil {
			koreaderData["data"].(map[string]interface{})["page"] = *book.Page
		}
		if book.TotalPages != nil {
			koreaderData["data"].(map[string]interface{})["total_pages"] = *book.TotalPages
		}

		existingData := map[string]interface{}{
			"source":    existingProgress.LastSyncSource.String,
			"timestamp": existingProgress.LastSyncTimestamp.Time,
			"data": map[string]interface{}{
				"percentage": existingProgress.Percentage.Float64,
			},
		}
		if existingProgress.Epubcfi.Valid {
			existingData["data"].(map[string]interface{})["epubcfi"] = existingProgress.Epubcfi.String
		}
		if existingProgress.Chapter.Valid {
			existingData["data"].(map[string]interface{})["chapter"] = existingProgress.Chapter.Int32
		}
		if existingProgress.CharacterOffset.Valid {
			existingData["data"].(map[string]interface{})["character"] = existingProgress.CharacterOffset.Int64
		}
		if existingProgress.CurrentPage.Valid {
			existingData["data"].(map[string]interface{})["page"] = existingProgress.CurrentPage.Int32
		}
		if existingProgress.TotalPages.Valid {
			existingData["data"].(map[string]interface{})["total_pages"] = existingProgress.TotalPages.Int32
		}

		conflictData := map[string]interface{}{
			"koreader": koreaderData,
			"existing": existingData,
		}
		conflictDataJSON, _ := json.Marshal(conflictData)

		_, err := h.db.CreateSyncConflict(ctx, database.CreateSyncConflictParams{
			MediaItemID:  mediaItemID,
			UserID:       userID,
			ConflictType: "progress",
			ConflictData: conflictDataJSON,
		})
		if err == nil {
			h.connManager.BroadcastConflictNotification(
				mediaItemID.Bytes,
				"detection",
				"",
			)
		}
	}

	deviceInfo := book.DeviceInfo
	if deviceInfo.DeviceModel == "" {
		deviceInfo.DeviceModel = "KOReader Device"
	}

	h.connManager.BroadcastProgressUpdate(
		uuid.UUID(mediaItemID.Bytes),
		book.Percentage,
		wsync.SourceDevice{
			ID:   uuid.UUID(userID.Bytes).String(),
			Name: deviceInfo.DeviceModel,
			Type: "koreader",
		},
	)

	_, err = h.db.UpdateDeviceLastSync(ctx, pgtype.UUID{Bytes: [16]byte{}, Valid: false})

	return err
}

func (h *KOReaderHandler) GetMetadata(c *echo.Context) error {
	device := c.Get("device").(database.Devices)
	userID := device.UserID.Bytes

	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}
	pgUserID := pgtype.UUID{Bytes: userID, 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",
		})
	}

	progress, err := h.db.GetUniversalProgress(c.Request().Context(), database.GetUniversalProgressParams{
		MediaItemID: pgBookUUID,
		UserID:      pgUserID,
	})

	if err != nil {
		return c.JSON(http.StatusOK, map[string]interface{}{
			"uuid":        bookUUID,
			"title":       mediaItem.Title,
			"author":      mediaItem.Author,
			"progress":    nil,
			"annotations": map[string][]interface{}{},
		})
	}

	progressData := KOReaderProgressData{
		Percentage: progress.Percentage.Float64,
	}

	if progress.Epubcfi.Valid {
		progressData.Epubcfi = &progress.Epubcfi.String
	}
	if progress.Chapter.Valid {
		ch := int(progress.Chapter.Int32)
		progressData.Chapter = &ch
	}
	if progress.ChapterProgress.Valid {
		cp := progress.ChapterProgress.Float64
		progressData.ChapterProgress = &cp
	}
	if progress.CharacterOffset.Valid {
		co := int64(progress.CharacterOffset.Int64)
		progressData.Character = &co
	}
	if progress.CurrentPage.Valid {
		cp := int(progress.CurrentPage.Int32)
		progressData.Page = &cp
	}
	if progress.TotalPages.Valid {
		tp := int(progress.TotalPages.Int32)
		progressData.TotalPages = &tp
	}

	annotations, err := h.db.GetAnnotationsForBook(c.Request().Context(), database.GetAnnotationsForBookParams{
		MediaItemID: pgBookUUID,
		UserID:      pgUserID,
	})

	annotationsResponse := KOReaderAnnotations{
		Highlights: []KOReaderHighlight{},
		Notes:      []KOReaderNote{},
		Bookmarks:  []KOReaderBookmark{},
	}

	for _, ann := range annotations {
		if ann.AnnotationType == "highlight" {
			annotationsResponse.Highlights = append(annotationsResponse.Highlights, KOReaderHighlight{
				Text:     ann.SelectionText,
				Pos0:     ann.StartPosition.String,
				Pos1:     ann.EndPosition.String,
				Color:    ann.Color.String,
				Datetime: ann.CreatedAt.Time.Format(time.RFC3339),
			})
		} else if ann.AnnotationType == "note" {
			annotationsResponse.Notes = append(annotationsResponse.Notes, KOReaderNote{
				Text:     ann.SelectionText,
				Pos0:     ann.StartPosition.String,
				Datetime: ann.CreatedAt.Time.Format(time.RFC3339),
			})
		}
	}

	lastSync := "never"
	if progress.LastSyncTimestamp.Valid {
		lastSync = progress.LastSyncTimestamp.Time.Format(time.RFC3339)
	}

	metadata := KOReaderMetadata{
		UUID:        bookUUID.String(),
		Title:       mediaItem.Title,
		Authors:     []string{mediaItem.Author.String},
		Progress:    progressData,
		Annotations: annotationsResponse,
		LastSync:    lastSync,
	}

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

func (h *KOReaderHandler) GetLibrary(c *echo.Context) error {
	device := c.Get("device").(database.Devices)
	userID := device.UserID.Bytes

	pgUserID := pgtype.UUID{Bytes: userID, Valid: true}

	mediaItems, err := h.db.GetUserMediaItemsForSync(c.Request().Context(), pgUserID)
	if err != nil {
		return c.JSON(http.StatusInternalServerError, map[string]string{
			"error": "failed to fetch library",
		})
	}

	libraryBooks := []KOReaderLibraryBook{}

	for _, item := range mediaItems {
		pgItemUUID := pgtype.UUID{Bytes: item.ID.Bytes, Valid: true}

		progress, err := h.db.GetUniversalProgress(c.Request().Context(), database.GetUniversalProgressParams{
			MediaItemID: pgItemUUID,
			UserID:      pgUserID,
		})

		percentRead := 0.0
		var pagesRemaining *int
		bookmarkCount := 0
		lastModified := time.Now().Format(time.RFC3339)

		if err == nil {
			percentRead = progress.Percentage.Float64
			if progress.TotalPages.Valid && progress.CurrentPage.Valid {
				remaining := int(progress.TotalPages.Int32 - progress.CurrentPage.Int32)
				pagesRemaining = &remaining
			}
			if progress.LastReadAt.Valid {
				lastModified = progress.LastReadAt.Time.Format(time.RFC3339)
			}
		}

		annotations, _ := h.db.GetAnnotationsForBook(c.Request().Context(), database.GetAnnotationsForBookParams{
			MediaItemID: pgItemUUID,
			UserID:      pgUserID,
		})
		bookmarkCount = len(annotations)

		libraryBooks = append(libraryBooks, KOReaderLibraryBook{
			UUID:           uuid.UUID(item.ID.Bytes).String(),
			Title:          item.Title,
			Author:         item.Author.String,
			ContentType:    "6",
			PercentRead:    percentRead * 100,
			PagesRemaining: pagesRemaining,
			BookmarkCount:  bookmarkCount,
			LastModified:   lastModified,
		})
	}

	return c.JSON(http.StatusOK, KOReaderLibraryResponse{
		LibrarySync: libraryBooks,
		TotalBooks:  len(libraryBooks),
		LastSync:    time.Now().Format(time.RFC3339),
	})
}

func (h *KOReaderHandler) SyncBookmarks(c *echo.Context) error {
	device := c.Get("device").(database.Devices)
	userID := device.UserID.Bytes

	var req struct {
		BookUUID   string              `json:"book_uuid,omitempty"`
		BookSHA256 string              `json:"book_sha256,omitempty"`
		Bookmarks  []KOReaderBookmark  `json:"bookmarks"`
		Notes      []KOReaderNote      `json:"notes"`
		Highlights []KOReaderHighlight `json:"highlights"`
	}

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

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

	ctx := c.Request().Context()
	pgUserID := pgtype.UUID{Bytes: userID, Valid: true}

	// Resolve media item ID using priority matching
	var pgBookUUID pgtype.UUID
	if req.BookUUID != "" {
		// Use UUID directly
		bookUUID, err := uuid.Parse(req.BookUUID)
		if err != nil {
			return c.JSON(http.StatusBadRequest, map[string]string{
				"error": "invalid book UUID",
			})
		}
		pgBookUUID = pgtype.UUID{Bytes: bookUUID, Valid: true}
	} else if req.BookSHA256 != "" && len(req.BookSHA256) == 64 {
		// Use SHA-256 to find book
		mediaItem, err := h.db.GetMediaItemBySHA256(ctx, pgtype.Text{String: req.BookSHA256, Valid: true})
		if err != nil {
			return c.JSON(http.StatusNotFound, map[string]string{
				"error": "book not found by SHA-256",
			})
		}
		pgBookUUID = mediaItem.ID
	} else {
		return c.JSON(http.StatusBadRequest, map[string]string{
			"error": "either book_uuid or book_sha256 is required",
		})
	}

	bookmarksSynced := 0
	notesSynced := 0
	highlightsSynced := 0

	for _, bookmark := range req.Bookmarks {
		mediaItemID := pgBookUUID

		// If bookmark has its own SHA-256, use it for matching
		if bookmark.BookSHA256 != "" && len(bookmark.BookSHA256) == 64 {
			mediaItem, err := h.db.GetMediaItemBySHA256(ctx, pgtype.Text{String: bookmark.BookSHA256, Valid: true})
			if err == nil {
				mediaItemID = mediaItem.ID
			}
		}

		position := ""
		if bookmark.Pos0 != "" {
			position = bookmark.Pos0
		} else if bookmark.Page > 0 {
			position = fmt.Sprintf("page:%d", bookmark.Page)
		}

		_, err := h.db.CreateMediaNote(ctx, database.CreateMediaNoteParams{
			MediaItemID: mediaItemID,
			UserID:      pgUserID,
			Content:     bookmark.Text,
			Position:    pgtype.Text{String: position, Valid: position != ""},
		})

		if err == nil {
			bookmarksSynced++
		}
	}

	for _, note := range req.Notes {
		mediaItemID := pgBookUUID

		// If note has its own SHA-256, use it for matching
		if note.BookSHA256 != "" && len(note.BookSHA256) == 64 {
			mediaItem, err := h.db.GetMediaItemBySHA256(ctx, pgtype.Text{String: note.BookSHA256, Valid: true})
			if err == nil {
				mediaItemID = mediaItem.ID
			}
		}

		position := ""
		if note.Pos0 != "" {
			position = note.Pos0
		} else if note.Page > 0 {
			position = fmt.Sprintf("page:%d", note.Page)
		}

		_, err := h.db.CreateMediaNote(ctx, database.CreateMediaNoteParams{
			MediaItemID: mediaItemID,
			UserID:      pgUserID,
			Content:     note.Notes,
			Position:    pgtype.Text{String: position, Valid: position != ""},
		})

		if err == nil {
			notesSynced++
		}
	}

	for _, highlight := range req.Highlights {
		mediaItemID := pgBookUUID

		// If highlight has its own SHA-256, use it for matching
		if highlight.BookSHA256 != "" && len(highlight.BookSHA256) == 64 {
			mediaItem, err := h.db.GetMediaItemBySHA256(ctx, pgtype.Text{String: highlight.BookSHA256, Valid: true})
			if err == nil {
				mediaItemID = mediaItem.ID
			}
		}

		startPos := highlight.Pos0
		endPos := highlight.Pos1
		if startPos == "" && highlight.Page > 0 {
			startPos = fmt.Sprintf("page:%d", highlight.Page)
			endPos = startPos
		}

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

		_, err := h.db.CreateMediaHighlight(ctx, database.CreateMediaHighlightParams{
			MediaItemID:   mediaItemID,
			UserID:        pgUserID,
			SelectionText: highlight.Text,
			StartPosition: pgtype.Text{String: startPos, Valid: startPos != ""},
			EndPosition:   pgtype.Text{String: endPos, Valid: endPos != ""},
			Color:         pgtype.Text{String: color, Valid: true},
		})

		if err == nil {
			highlightsSynced++
		}
	}

	return c.JSON(http.StatusOK, map[string]interface{}{
		"sync_status":       "completed",
		"bookmarks_synced":  bookmarksSynced,
		"notes_synced":      notesSynced,
		"highlights_synced": highlightsSynced,
		"total_synced":      bookmarksSynced + notesSynced + highlightsSynced,
		"timestamp":         time.Now().Format(time.RFC3339),
	})
}
