package handlers

import (
	"bookhoard/internal/database"
	wsync "bookhoard/internal/sync"
	"crypto/sha256"
	"encoding/hex"
	"encoding/json"
	"fmt"
	"net/http"
	"regexp"
	"strings"
	"time"

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

type KoboHandler struct {
	db          *database.Queries
	connManager *wsync.ConnectionManager
}

func NewKoboHandler(db *database.Queries, connManager *wsync.ConnectionManager) *KoboHandler {
	return &KoboHandler{db: db, connManager: connManager}
}

// mapContentIdToBookhoardUUID maps Kobo ContentId to Bookhoard UUID with multiple fallback strategies
// Enhanced Kobo Sync - ContentId Mapping Logic
func (h *KoboHandler) mapContentIdToBookhoardUUID(ctx *echo.Context, contentId string, deviceID uuid.UUID) (uuid.UUID, error, string) {
	// Step 1: Try direct ContentId lookup in device_catalogs table
	catalog, err := h.db.GetDeviceCatalogByKoboContentId(ctx.Request().Context(), contentId)
	if err == nil && catalog.ID.Valid {
		// Found! Use canonical Bookhoard UUID
		return uuid.UUID(catalog.BookhoardUuid.Bytes), nil, "catalog_match"
	}

	// Step 2: ContentId not found - check if it looks like a SHA-256 hash
	if len(contentId) == 64 && looksLikeSHA256(contentId) {
		// Try to find media item by SHA-256
		mediaItem, err := h.db.GetMediaItemBySHA256(ctx.Request().Context(), pgtype.Text{String: contentId, Valid: true})
		if err == nil {
			// Found by SHA-256! Create device catalog entry for future lookups
			_, _ = h.db.CreateDeviceCatalog(ctx.Request().Context(), database.CreateDeviceCatalogParams{
				DeviceID:       pgtype.UUID{Bytes: deviceID, Valid: true},
				MediaItemID:    pgtype.UUID{Bytes: mediaItem.ID.Bytes, Valid: true},
				BookhoardUuid:  pgtype.UUID{Bytes: mediaItem.ID.Bytes, Valid: true},
				KoboContentID:  contentId,
				ContentIDType:  pgtype.Text{String: "sha256", Valid: true},
				Available:      pgtype.Bool{Bool: true, Valid: true},
				DeliveryDate:   pgtype.Timestamptz{Time: time.Now(), Valid: true},
				DeliveryMethod: pgtype.Text{String: "sync", Valid: true},
			})
			return uuid.UUID(mediaItem.ID.Bytes), nil, "sha256_match"
		}
	}

	// Step 3: Try to parse as UUID directly
	if parsedUUID, err := uuid.Parse(contentId); err == nil {
		// Check if this UUID exists in media_items
		mediaItem, err := h.db.GetMediaItem(ctx.Request().Context(), pgtype.UUID{Bytes: parsedUUID, Valid: true})
		if err == nil {
			// Create device catalog entry
			_, _ = h.db.CreateDeviceCatalog(ctx.Request().Context(), database.CreateDeviceCatalogParams{
				DeviceID:       pgtype.UUID{Bytes: deviceID, Valid: true},
				MediaItemID:    pgtype.UUID{Bytes: mediaItem.ID.Bytes, Valid: true},
				BookhoardUuid:  pgtype.UUID{Bytes: mediaItem.ID.Bytes, Valid: true},
				KoboContentID:  contentId,
				ContentIDType:  pgtype.Text{String: "bookhoard_uuid", Valid: true},
				Available:      pgtype.Bool{Bool: true, Valid: true},
				DeliveryDate:   pgtype.Timestamptz{Time: time.Now(), Valid: true},
				DeliveryMethod: pgtype.Text{String: "sync", Valid: true},
			})
			return parsedUUID, nil, "uuid_match"
		}
	}

	// Step 4: Not found - return error for unlinked book
	return uuid.Nil, fmt.Errorf("unlinked book: ContentId %s not found", contentId), "unlinked"
}

// mapBookhoardUUIDToKoboContentId maps Bookhoard UUID to Kobo ContentId
// Creates new entry in device_catalogs if not exists
func (h *KoboHandler) mapBookhoardUUIDToKoboContentId(c *echo.Context, bookhoardUUID uuid.UUID, deviceID uuid.UUID) (string, error) {
	// Check if catalog entry already exists
	catalog, err := h.db.GetDeviceCatalogByBookhoardUUID(c.Request().Context(), database.GetDeviceCatalogByBookhoardUUIDParams{
		DeviceID:      pgtype.UUID{Bytes: deviceID, Valid: true},
		BookhoardUuid: pgtype.UUID{Bytes: bookhoardUUID, Valid: true},
	})
	if err == nil && catalog.ID.Valid {
		return catalog.KoboContentID, nil
	}

	// Get media item to check for existing Kobo content ID
	mediaItem, err := h.db.GetMediaItem(c.Request().Context(), pgtype.UUID{Bytes: bookhoardUUID, Valid: true})
	if err != nil {
		return "", err
	}

	// Generate Kobo ContentId based on priority:
	// 1. Use existing kobo_content_id from media_items
	// 2. Use existing entitlement_id from media_items
	// 3. Generate new "kobo_" prefixed UUID
	var koboContentId string
	contentIdType := "bookhoard_generated"

	if mediaItem.KoboContentID.Valid && mediaItem.KoboContentID.String != "" {
		koboContentId = mediaItem.KoboContentID.String
		contentIdType = "kobo_metadata"
	} else if mediaItem.EntitlementID.Valid && mediaItem.EntitlementID.String != "" {
		koboContentId = mediaItem.EntitlementID.String
		contentIdType = "entitlement_id"
	} else {
		koboContentId = "kobo_" + uuid.New().String()
		contentIdType = "kobo_generated"
	}

	// Create device catalog entry
	_, err = h.db.CreateDeviceCatalog(c.Request().Context(), database.CreateDeviceCatalogParams{
		DeviceID:       pgtype.UUID{Bytes: deviceID, Valid: true},
		MediaItemID:    pgtype.UUID{Bytes: bookhoardUUID, Valid: true},
		BookhoardUuid:  pgtype.UUID{Bytes: bookhoardUUID, Valid: true},
		KoboContentID:  koboContentId,
		ContentIDType:  pgtype.Text{String: contentIdType, Valid: true},
		Available:      pgtype.Bool{Bool: true, Valid: true},
		DeliveryDate:   pgtype.Timestamptz{Time: time.Now(), Valid: true},
		DeliveryMethod: pgtype.Text{String: "opds", Valid: true},
	})

	if err != nil {
		return "", err
	}

	return koboContentId, nil
}

// getCollectionMetadataForBook retrieves collection names for a book
func (h *KoboHandler) getCollectionMetadataForBook(c *echo.Context, bookhoardUUID uuid.UUID, deviceID uuid.UUID) ([]string, error) {
	device := c.Get("device").(database.Devices)
	pgDeviceID := pgtype.UUID{Bytes: device.ID.Bytes, Valid: true}

	// Get collections for this book
	collections, err := h.db.GetCollectionsForBook(c.Request().Context(), pgtype.UUID{Bytes: bookhoardUUID, Valid: true})
	if err != nil {
		return []string{}, nil
	}

	var collectionNames []string

	// For each collection, check if there's a device-specific shelf mapping
	for _, collection := range collections {
		mapping, err := h.db.GetDeviceShelfMapping(c.Request().Context(), database.GetDeviceShelfMappingParams{
			DeviceID:     pgDeviceID,
			CollectionID: pgtype.UUID{Bytes: collection.ID.Bytes, Valid: true},
		})
		if err == nil && mapping.ID.Valid && mapping.DeviceShelfName.Valid {
			// Use device-specific shelf name
			collectionNames = append(collectionNames, mapping.DeviceShelfName.String)
		} else if collection.Name != "" {
			// Fall back to collection name
			collectionNames = append(collectionNames, collection.Name)
		}
	}

	return collectionNames, nil
}

// looksLikeSHA256 checks if a string looks like a SHA-256 hash
func looksLikeSHA256(s string) bool {
	if len(s) != 64 {
		return false
	}
	matched, _ := regexp.MatchString("^[0-9a-fA-F]{64}$", s)
	return matched
}

// calculateFileSHA256 calculates SHA-256 hash of file path
func calculateFileSHA256(filePath string) string {
	hash := sha256.Sum256([]byte(filePath))
	return hex.EncodeToString(hash[:])
}

type KoboDeviceInfo struct {
	DeviceID     string `json:"DeviceId"`
	Model        string `json:"Model"`
	SerialNumber string `json:"SerialNumber"`
	Firmware     string `json:"Firmware,omitempty"`
}

type KoboReadingSync struct {
	ContentId            string  `json:"ContentId"`
	PercentRead          float64 `json:"PercentRead"`
	EntitlementId        string  `json:"EntitlementId"`
	RemainingTimeMinutes int     `json:"RemainingTimeMinutes"`
	FirstReadTime        string  `json:"FirstReadTime,omitempty"`
	LastModified         string  `json:"LastModified"`
	ContentType          string  `json:"ContentType,omitempty"`
}

type KoboBookmarkSync struct {
	BookmarkId    string `json:"BookmarkId"`
	ContentId     string `json:"ContentId"`
	BookmarkText  string `json:"BookmarkText"`
	BookmarkType  string `json:"BookmarkType"`
	BookmarkTitle string `json:"BookmarkTitle"`
	DateCreated   string `json:"DateCreated"`
	Chapter       int    `json:"Chapter,omitempty"`
	Hidden        bool   `json:"Hidden,omitempty"`
}

type KoboMarkupRequest struct {
	ReadingSync  []KoboReadingSync  `json:"ReadingSync"`
	BookmarkSync []KoboBookmarkSync `json:"BookmarkSync,omitempty"`
}

type KoboLibraryBook struct {
	ContentId      string   `json:"ContentId"`
	ContentType    string   `json:"ContentType"`
	Title          string   `json:"Title"`
	Author         string   `json:"Author"`
	PercentRead    float64  `json:"PercentRead"`
	PagesRemaining *int     `json:"PagesRemaining,omitempty"`
	BookmarkCount  int      `json:"BookmarkCount"`
	LastModified   string   `json:"LastModified"`
	EntitlementId  string   `json:"EntitlementId,omitempty"`
	Revision       int      `json:"Revision"`
	MimeType       string   `json:"MimeType"`
	FileSize       int64    `json:"FileSize"`
	Categories     []string `json:"Categories,omitempty"`
	BookhoardUUID  string   `json:"BookhoardUUID,omitempty"`
}

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

type KoboInitResponse struct {
	Resources map[string]interface{} `json:"Resources"`
	UserKey   string                 `json:"UserKey"`
}

type KoboSyncStatus struct {
	Status          string `json:"Status"`
	MarkupsSynced   int    `json:"MarkupsSynced"`
	BookmarksSynced int    `json:"BookmarksSynced"`
}

type KoboServerSyncData struct {
	ContentId    string             `json:"ContentId"`
	PercentRead  float64            `json:"PercentRead"`
	Bookmarks    []KoboBookmarkSync `json:"Bookmarks,omitempty"`
	Highlights   []KoboBookmarkSync `json:"Highlights,omitempty"`
	LastModified string             `json:"LastModified"`
}

type KoboServerSyncResponse struct {
	BooksSynced    int `json:"BooksSynced"`
	BookmarksSent  int `json:"BookmarksSent"`
	HighlightsSent int `json:"HighlightsSent"`
}

type KoboAnalyticsTest struct {
	ContentId        string  `json:"ContentId"`
	ReadingEvent     string  `json:"ReadingEvent"`
	RemainingTimeMin int     `json:"RemainingTimeMin"`
	PercentRead      float64 `json:"PercentRead"`
}

func (h *KoboHandler) Initialization(c *echo.Context) error {
	device := c.Get("device").(database.Devices)
	userID := device.UserID.Bytes
	deviceID := device.ID.Bytes

	pgUserID := pgtype.UUID{Bytes: userID, Valid: true}
	deviceUUID := uuid.UUID(deviceID)

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

	librarySync := []KoboLibraryBook{}
	for _, item := range mediaItems {
		bookhoardUUID := uuid.UUID(item.ID.Bytes)

		progress, _ := h.db.GetUniversalProgress(c.Request().Context(), database.GetUniversalProgressParams{
			MediaItemID: pgtype.UUID{Bytes: item.ID.Bytes, Valid: true},
			UserID:      pgUserID,
		})

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

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

		bookmarkCount := 0
		annotations, _ := h.db.GetAnnotationsForBook(c.Request().Context(), database.GetAnnotationsForBookParams{
			MediaItemID: pgtype.UUID{Bytes: item.ID.Bytes, Valid: true},
			UserID:      pgUserID,
		})
		bookmarkCount = len(annotations)

		author := ""
		if item.Author.Valid {
			author = item.Author.String
		}

		// Use ContentId mapping instead of direct UUID
		koboContentId, err := h.mapBookhoardUUIDToKoboContentId(c, bookhoardUUID, deviceUUID)
		if err != nil {
			// Fallback to entitlement_id or generate new one
			if item.EntitlementID.Valid && item.EntitlementID.String != "" {
				koboContentId = item.EntitlementID.String
			} else {
				koboContentId = "kobo_" + bookhoardUUID.String()
			}
		}

		mimeType := item.MimeType.String
		if !item.MimeType.Valid {
			mimeType = ""
		}

		fileSize := int64(0)
		if item.FileSize.Valid {
			fileSize = item.FileSize.Int64
		}

		revision := 1
		if item.RevisionNumber.Valid {
			revision = int(item.RevisionNumber.Int32)
		}

		contentType := "6"
		if strings.Contains(mimeType, "pdf") {
			contentType = "5"
		}

		// Get collection metadata for this book
		collections, _ := h.getCollectionMetadataForBook(c, bookhoardUUID, deviceUUID)

		librarySync = append(librarySync, KoboLibraryBook{
			ContentId:      koboContentId,
			ContentType:    contentType,
			Title:          item.Title,
			Author:         author,
			PercentRead:    percentRead,
			PagesRemaining: pagesRemaining,
			BookmarkCount:  bookmarkCount,
			LastModified:   lastModified,
			EntitlementId:  koboContentId,
			Revision:       revision,
			MimeType:       mimeType,
			FileSize:       fileSize,
			Categories:     collections,
			BookhoardUUID:  bookhoardUUID.String(),
		})
	}

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

func (h *KoboHandler) LibrarySync(c *echo.Context) error {
	return h.Initialization(c)
}

func (h *KoboHandler) Markup(c *echo.Context) error {
	device := c.Get("device").(database.Devices)
	userID := device.UserID.Bytes
	deviceID := device.ID.Bytes

	pgUserID := pgtype.UUID{Bytes: userID, Valid: true}
	deviceUUID := uuid.UUID(deviceID)

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

	markupsSynced := 0
	bookmarksSynced := 0
	unlinkedBooks := 0

	for _, readingSync := range req.ReadingSync {
		// Use ContentId mapping with fallback logic
		bookhoardUUID, err, _ := h.mapContentIdToBookhoardUUID(c, readingSync.ContentId, deviceUUID)
		if err != nil || bookhoardUUID == uuid.Nil {
			// Unlinked book detected
			unlinkedBooks++
			// TODO: Create unlinked book entry for manual resolution
			continue
		}

		pgMediaUUID := pgtype.UUID{Bytes: bookhoardUUID, Valid: true}

		percentage := readingSync.PercentRead / 100.0

		_, err = h.db.UpdateUniversalProgress(c.Request().Context(), database.UpdateUniversalProgressParams{
			MediaItemID:    pgMediaUUID,
			UserID:         pgUserID,
			Percentage:     pgtype.Float8{Float64: percentage, Valid: true},
			LastSyncDevice: pgtype.Text{String: "kobo", Valid: true},
			LastSyncSource: pgtype.Text{String: "kobo", Valid: true},
		})

		if err == nil {
			markupsSynced++

			h.connManager.BroadcastProgressUpdate(
				bookhoardUUID,
				percentage,
				wsync.SourceDevice{
					ID:   uuid.UUID(userID).String(),
					Name: device.DeviceName,
					Type: "kobo",
				},
			)
		}
	}

	for _, bookmarkSync := range req.BookmarkSync {
		// Use ContentId mapping with fallback logic
		bookhoardUUID, err, _ := h.mapContentIdToBookhoardUUID(c, bookmarkSync.ContentId, deviceUUID)
		if err != nil || bookhoardUUID == uuid.Nil {
			// Unlinked book - skip
			continue
		}

		pgMediaUUID := pgtype.UUID{Bytes: bookhoardUUID, Valid: true}

		switch bookmarkSync.BookmarkType {
		case "annotation":
			if bookmarkSync.BookmarkText != "" {
				h.db.CreateMediaHighlight(c.Request().Context(), database.CreateMediaHighlightParams{
					MediaItemID:   pgMediaUUID,
					UserID:        pgUserID,
					SelectionText: bookmarkSync.BookmarkText,
					StartPosition: pgtype.Text{String: bookmarkSync.BookmarkId, Valid: true},
					EndPosition:   pgtype.Text{String: bookmarkSync.BookmarkId, Valid: true},
					Color:         pgtype.Text{String: "#ffff00", Valid: true},
				})
				bookmarksSynced++
			}
		case "bookmark":
			if bookmarkSync.BookmarkText != "" {
				h.db.CreateMediaNote(c.Request().Context(), database.CreateMediaNoteParams{
					MediaItemID: pgMediaUUID,
					UserID:      pgUserID,
					Content:     bookmarkSync.BookmarkText,
					Position:    pgtype.Text{String: bookmarkSync.BookmarkId, Valid: true},
				})
				bookmarksSynced++
			}
		case "last-read-place":
			if bookmarkSync.BookmarkId != "" {
				var epubcfi string

				if strings.HasPrefix(bookmarkSync.BookmarkId, "epubcfi(") {
					epubcfi = strings.TrimPrefix(bookmarkSync.BookmarkId, "epubcfi(")
					epubcfi = strings.TrimSuffix(epubcfi, ")")
				}

				_, err = h.db.UpdateUniversalProgress(c.Request().Context(), database.UpdateUniversalProgressParams{
					MediaItemID:     pgMediaUUID,
					UserID:          pgUserID,
					Epubcfi:         pgtype.Text{String: epubcfi, Valid: true},
					Chapter:         pgtype.Int4{Int32: int32(bookmarkSync.Chapter), Valid: true},
					ChapterProgress: pgtype.Float8{Float64: 0.5, Valid: true},
					LastSyncDevice:  pgtype.Text{String: "kobo", Valid: true},
					LastSyncSource:  pgtype.Text{String: "kobo", Valid: true},
				})
				if err != nil {
					fmt.Printf("Failed to store last-read-place: %v", err)
				}
				bookmarksSynced++
			}
		}
	}

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

	response := KoboSyncStatus{
		Status:          "Success",
		MarkupsSynced:   markupsSynced,
		BookmarksSynced: bookmarksSynced,
	}

	// Include unlinked books count if any
	if unlinkedBooks > 0 {
		// For now, just log it. In production, this should trigger an alert
		// and the unlinked books should be stored for manual resolution
		response.Status = "Partial"
	}

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

func (h *KoboHandler) Bookmark(c *echo.Context) error {
	device := c.Get("device").(database.Devices)
	userID := device.UserID.Bytes
	deviceID := device.ID.Bytes

	pgUserID := pgtype.UUID{Bytes: userID, Valid: true}
	deviceUUID := uuid.UUID(deviceID)

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

	bookmarksSynced := 0

	for _, bookmarkSync := range req.BookmarkSync {
		// Use ContentId mapping with fallback logic
		bookhoardUUID, err, _ := h.mapContentIdToBookhoardUUID(c, bookmarkSync.ContentId, deviceUUID)
		if err != nil || bookhoardUUID == uuid.Nil {
			// Unlinked book - skip
			continue
		}

		pgMediaUUID := pgtype.UUID{Bytes: bookhoardUUID, Valid: true}

		switch bookmarkSync.BookmarkType {
		case "annotation":
			if bookmarkSync.BookmarkText != "" {
				h.db.CreateMediaHighlight(c.Request().Context(), database.CreateMediaHighlightParams{
					MediaItemID:   pgMediaUUID,
					UserID:        pgUserID,
					SelectionText: bookmarkSync.BookmarkText,
					StartPosition: pgtype.Text{String: bookmarkSync.BookmarkId, Valid: true},
					EndPosition:   pgtype.Text{String: bookmarkSync.BookmarkId, Valid: true},
					Color:         pgtype.Text{String: "#ffff00", Valid: true},
				})
				bookmarksSynced++
			}
		case "bookmark":
			if bookmarkSync.BookmarkText != "" {
				h.db.CreateMediaNote(c.Request().Context(), database.CreateMediaNoteParams{
					MediaItemID: pgMediaUUID,
					UserID:      pgUserID,
					Content:     bookmarkSync.BookmarkText,
					Position:    pgtype.Text{String: bookmarkSync.BookmarkId, Valid: true},
				})
				bookmarksSynced++
			}
		}
	}

	_, 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.StatusOK, KoboSyncStatus{
		Status:          "Success",
		BookmarksSynced: bookmarksSynced,
		MarkupsSynced:   0,
	})
}

func (h *KoboHandler) AnalyticsGettests(c *echo.Context) error {
	device := c.Get("device").(database.Devices)
	userID := device.UserID.Bytes
	deviceID := device.ID.Bytes

	pgUserID := pgtype.UUID{Bytes: userID, Valid: true}
	deviceUUID := uuid.UUID(deviceID)

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

	for _, test := range req {
		// Use ContentId mapping with fallback logic
		bookhoardUUID, err, _ := h.mapContentIdToBookhoardUUID(c, test.ContentId, deviceUUID)
		if err != nil || bookhoardUUID == uuid.Nil {
			// Unlinked book - skip
			continue
		}

		pgMediaUUID := pgtype.UUID{Bytes: bookhoardUUID, Valid: true}
		percentage := test.PercentRead / 100.0

		_, err = h.db.UpdateUniversalProgress(c.Request().Context(), database.UpdateUniversalProgressParams{
			MediaItemID:    pgMediaUUID,
			UserID:         pgUserID,
			Percentage:     pgtype.Float8{Float64: percentage, Valid: true},
			LastSyncDevice: pgtype.Text{String: "kobo", Valid: true},
			LastSyncSource: pgtype.Text{String: "kobo", Valid: true},
		})

		if err == nil {
			h.connManager.BroadcastProgressUpdate(
				bookhoardUUID,
				percentage,
				wsync.SourceDevice{
					ID:   uuid.UUID(userID).String(),
					Name: device.DeviceName,
					Type: "kobo",
				},
			)
		}
	}

	_, 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.StatusOK, map[string]interface{}{
		"Status": "Success",
	})
}

func parseKoboDeviceHeader(c *echo.Context) (KoboDeviceInfo, error) {
	deviceHeader := c.Request().Header.Get("x-kobo-device")
	if deviceHeader == "" {
		return KoboDeviceInfo{}, fmt.Errorf("missing x-kobo-device header")
	}

	var device KoboDeviceInfo
	if err := json.Unmarshal([]byte(deviceHeader), &device); err != nil {
		return KoboDeviceInfo{}, fmt.Errorf("invalid device header format")
	}

	return device, nil
}

func (h *KoboHandler) SyncFromServer(c *echo.Context) error {
	device := c.Get("device").(database.Devices)
	userID := device.UserID.Bytes
	deviceID := device.ID.Bytes

	pgUserID := pgtype.UUID{Bytes: userID, Valid: true}
	deviceUUID := uuid.UUID(deviceID)

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

	booksSynced := 0
	bookmarksSent := 0
	highlightsSent := 0

	for _, syncData := range req {
		// Use ContentId mapping with fallback logic
		bookhoardUUID, err, _ := h.mapContentIdToBookhoardUUID(c, syncData.ContentId, deviceUUID)
		if err != nil || bookhoardUUID == uuid.Nil {
			// Unlinked book - skip
			continue
		}

		pgMediaUUID := pgtype.UUID{Bytes: bookhoardUUID, Valid: true}

		percentage := syncData.PercentRead / 100.0

		_, err = h.db.UpdateUniversalProgress(c.Request().Context(), database.UpdateUniversalProgressParams{
			MediaItemID:    pgMediaUUID,
			UserID:         pgUserID,
			Percentage:     pgtype.Float8{Float64: percentage, Valid: true},
			LastSyncDevice: pgtype.Text{String: "kobo", Valid: true},
			LastSyncSource: pgtype.Text{String: "bookhoard", Valid: true},
		})

		if err == nil {
			booksSynced++
		}

		for _, bookmark := range syncData.Bookmarks {
			if bookmark.BookmarkType == "bookmark" {
				h.db.CreateMediaNote(c.Request().Context(), database.CreateMediaNoteParams{
					MediaItemID: pgMediaUUID,
					UserID:      pgUserID,
					Content:     bookmark.BookmarkText,
					Position:    pgtype.Text{String: bookmark.BookmarkId, Valid: true},
				})
				bookmarksSent++
			} else if bookmark.BookmarkType == "annotation" {
				h.db.CreateMediaHighlight(c.Request().Context(), database.CreateMediaHighlightParams{
					MediaItemID:   pgMediaUUID,
					UserID:        pgUserID,
					SelectionText: bookmark.BookmarkText,
					StartPosition: pgtype.Text{String: bookmark.BookmarkId, Valid: true},
					EndPosition:   pgtype.Text{String: bookmark.BookmarkId, Valid: true},
					Color:         pgtype.Text{String: "#ffff00", Valid: true},
				})
				highlightsSent++
			}
		}

		for _, highlight := range syncData.Highlights {
			h.db.CreateMediaHighlight(c.Request().Context(), database.CreateMediaHighlightParams{
				MediaItemID:   pgMediaUUID,
				UserID:        pgUserID,
				SelectionText: highlight.BookmarkText,
				StartPosition: pgtype.Text{String: highlight.BookmarkId, Valid: true},
				EndPosition:   pgtype.Text{String: highlight.BookmarkId, Valid: true},
				Color:         pgtype.Text{String: "#ffff00", Valid: true},
			})
			highlightsSent++
		}
	}

	_, 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.StatusOK, KoboServerSyncResponse{
		BooksSynced:    booksSynced,
		BookmarksSent:  bookmarksSent,
		HighlightsSent: highlightsSent,
	})
}
