package handlers

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

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

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

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

type ConflictResolutionRequest struct {
	Winner     string                 `json:"winner" validate:"required"`
	ManualData map[string]interface{} `json:"manual_data"`
	ApplyToAll bool                   `json:"apply_to_all_future_conflicts"`
	Reason     string                 `json:"reason"`
}

type ConflictSourceData struct {
	Source    string                 `json:"source"`
	Timestamp time.Time              `json:"timestamp"`
	Data      map[string]interface{} `json:"data"`
}

type ConflictDetailResponse struct {
	ID               string                        `json:"id"`
	MediaItemID      string                        `json:"media_item_id"`
	MediaItemTitle   string                        `json:"media_item_title"`
	ConflictType     string                        `json:"conflict_type"`
	ConflictData     map[string]ConflictSourceData `json:"conflict_data"`
	ResolutionStatus string                        `json:"resolution_status"`
	ResolutionData   map[string]interface{}        `json:"resolution_data,omitempty"`
	ResolvedBy       string                        `json:"resolved_by,omitempty"`
	ResolvedAt       *time.Time                    `json:"resolved_at,omitempty"`
	CreatedAt        time.Time                     `json:"created_at"`
}

type ConflictListResponse struct {
	Conflicts  []ConflictDetailResponse `json:"conflicts"`
	Total      int                      `json:"total"`
	Unresolved int                      `json:"unresolved"`
}

type ConflictResolveResponse struct {
	ConflictResolved bool            `json:"conflict_resolved"`
	AppliedTo        map[string]bool `json:"applied_to"`
	DevicesSynced    []string        `json:"devices_synced"`
}

func (h *ConflictHandler) GetConflictsData(c *echo.Context) ([]ConflictDetailResponse, int, int, error) {
	user := c.Get("user").(database.Users)

	status := c.QueryParam("status")
	if status == "" {
		status = "unresolved"
	}

	ctx := context.Background()

	var conflicts interface{}
	var err error

	if status == "all" {
		conflicts, err = h.db.ListSyncConflictsByUser(ctx, user.ID)
	} else {
		conflicts, err = h.db.ListSyncConflictsByUser(ctx, user.ID)
	}

	if err != nil && !errors.Is(err, pgx.ErrNoRows) {
		return nil, 0, 0, err
	}

	response := make([]ConflictDetailResponse, 0, len(conflicts.([]database.ListSyncConflictsByUserRow)))
	unresolvedCount := 0

	for _, conflict := range conflicts.([]database.ListSyncConflictsByUserRow) {
		var conflictData map[string]ConflictSourceData
		if err := json.Unmarshal(conflict.ConflictData, &conflictData); err != nil {
			continue
		}

		detail := ConflictDetailResponse{
			ID:               uuid.UUID(conflict.ID.Bytes).String(),
			MediaItemID:      uuid.UUID(conflict.MediaItemID.Bytes).String(),
			MediaItemTitle:   conflict.Title,
			ConflictType:     conflict.ConflictType,
			ConflictData:     conflictData,
			ResolutionStatus: conflict.ResolutionStatus.String,
			CreatedAt:        conflict.CreatedAt.Time,
		}

		if conflict.ResolvedBy.Valid {
			detail.ResolvedBy = uuid.UUID(conflict.ResolvedBy.Bytes).String()
		}
		if conflict.ResolvedAt.Valid {
			detail.ResolvedAt = &conflict.ResolvedAt.Time
		}
		if conflict.ResolutionData != nil {
			if err := json.Unmarshal(conflict.ResolutionData, &detail.ResolutionData); err == nil {
			}
		}

		response = append(response, detail)
		if conflict.ResolutionStatus.String == "unresolved" {
			unresolvedCount++
		}
	}

	return response, len(response), unresolvedCount, nil
}

func (h *ConflictHandler) ListConflicts(c *echo.Context) error {
	conflicts, total, unresolved, err := h.GetConflictsData(c)
	if err != nil {
		return echo.NewHTTPError(http.StatusInternalServerError, "failed to list conflicts")
	}

	return c.JSON(http.StatusOK, ConflictListResponse{
		Conflicts:  conflicts,
		Total:      total,
		Unresolved: unresolved,
	})
}

func (h *ConflictHandler) GetConflict(c *echo.Context) error {
	user := c.Get("user").(database.Users)

	conflictID, err := uuid.Parse(c.Param("id"))
	if err != nil {
		return echo.NewHTTPError(http.StatusBadRequest, "invalid conflict ID")
	}

	conflictUUID := pgtype.UUID{Bytes: [16]byte(conflictID), Valid: true}
	conflict, err := h.db.GetSyncConflict(context.Background(), conflictUUID)
	if err != nil {
		if errors.Is(err, pgx.ErrNoRows) {
			return echo.NewHTTPError(http.StatusNotFound, "conflict not found")
		}
		return echo.NewHTTPError(http.StatusInternalServerError, "failed to get conflict")
	}

	if conflict.UserID.Bytes != user.ID.Bytes {
		return echo.NewHTTPError(http.StatusForbidden, "access denied")
	}

	mediaItem, err := h.db.GetMediaItem(context.Background(), conflict.MediaItemID)
	if err != nil {
		return echo.NewHTTPError(http.StatusInternalServerError, "failed to get media item")
	}

	var conflictData map[string]ConflictSourceData
	if err := json.Unmarshal(conflict.ConflictData, &conflictData); err != nil {
		return echo.NewHTTPError(http.StatusInternalServerError, "failed to parse conflict data")
	}

	detail := ConflictDetailResponse{
		ID:               uuid.UUID(conflict.ID.Bytes).String(),
		MediaItemID:      uuid.UUID(conflict.MediaItemID.Bytes).String(),
		MediaItemTitle:   mediaItem.Title,
		ConflictType:     conflict.ConflictType,
		ConflictData:     conflictData,
		ResolutionStatus: conflict.ResolutionStatus.String,
		CreatedAt:        conflict.CreatedAt.Time,
	}

	if conflict.ResolvedBy.Valid {
		detail.ResolvedBy = uuid.UUID(conflict.ResolvedBy.Bytes).String()
	}
	if conflict.ResolvedAt.Valid {
		detail.ResolvedAt = &conflict.ResolvedAt.Time
	}
	if conflict.ResolutionData != nil {
		if err := json.Unmarshal(conflict.ResolutionData, &detail.ResolutionData); err == nil {
		}
	}

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

func (h *ConflictHandler) ResolveConflict(c *echo.Context) error {
	user := c.Get("user").(database.Users)

	conflictID, err := uuid.Parse(c.Param("id"))
	if err != nil {
		return echo.NewHTTPError(http.StatusBadRequest, "invalid conflict ID")
	}

	var req ConflictResolutionRequest
	if err := c.Bind(&req); err != nil {
		return echo.NewHTTPError(http.StatusBadRequest, "invalid request body")
	}

	if req.Winner == "manual" && req.ManualData == nil {
		return echo.NewHTTPError(http.StatusBadRequest, "manual_data required when winner is manual")
	}

	conflictUUID := pgtype.UUID{Bytes: [16]byte(conflictID), Valid: true}
	conflict, err := h.db.GetSyncConflict(context.Background(), conflictUUID)
	if err != nil {
		if errors.Is(err, pgx.ErrNoRows) {
			return echo.NewHTTPError(http.StatusNotFound, "conflict not found")
		}
		return echo.NewHTTPError(http.StatusInternalServerError, "failed to get conflict")
	}

	if conflict.UserID.Bytes != user.ID.Bytes {
		return echo.NewHTTPError(http.StatusForbidden, "access denied")
	}

	if conflict.ResolutionStatus.String == "user_resolved" {
		return echo.NewHTTPError(http.StatusBadRequest, "conflict already resolved")
	}

	var conflictData map[string]ConflictSourceData
	if err := json.Unmarshal(conflict.ConflictData, &conflictData); err != nil {
		return echo.NewHTTPError(http.StatusInternalServerError, "failed to parse conflict data")
	}

	winnerData := map[string]interface{}{}
	winnerSource := req.Winner
	if req.Winner == "manual" {
		winnerData = req.ManualData
		winnerSource = "manual"
	} else {
		source, ok := conflictData[req.Winner]
		if !ok {
			return echo.NewHTTPError(http.StatusBadRequest, "invalid winner source")
		}
		winnerData = source.Data
	}

	appliedTo := map[string]bool{
		"progress":    false,
		"annotations": false,
	}

	if conflict.ConflictType == "progress" {
		if err := h.applyProgressResolution(conflict.MediaItemID, conflict.UserID, winnerSource, winnerData); err == nil {
			appliedTo["progress"] = true
		}
	}

	if conflict.ConflictType == "annotation_highlight" || conflict.ConflictType == "annotation_bookmark" || conflict.ConflictType == "annotation_note" {
		if err := h.applyAnnotationResolution(conflict.MediaItemID, conflict.UserID, winnerData, conflict.ConflictType); err == nil {
			appliedTo["annotations"] = true
		}
	}

	resolutionData := map[string]interface{}{
		"winner":      req.Winner,
		"applied_to":  appliedTo,
		"reason":      req.Reason,
		"resolved_at": time.Now(),
	}

	resolutionDataJSON, _ := json.Marshal(resolutionData)

	_, err = h.db.ResolveSyncConflict(context.Background(), database.ResolveSyncConflictParams{
		ID:               conflictUUID,
		ResolutionStatus: pgtype.Text{String: "user_resolved", Valid: true},
		ResolutionData:   resolutionDataJSON,
		ResolvedBy:       pgtype.UUID{Bytes: user.ID.Bytes, Valid: true},
	})
	if err != nil {
		return echo.NewHTTPError(http.StatusInternalServerError, "failed to resolve conflict")
	}

	devicesSynced := h.notifyDevicesOfResolution(conflict.MediaItemID, winnerData)

	response := ConflictResolveResponse{
		ConflictResolved: true,
		AppliedTo:        appliedTo,
		DevicesSynced:    devicesSynced,
	}

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

func (h *ConflictHandler) applyProgressResolution(mediaItemID pgtype.UUID, userID pgtype.UUID, winnerSource string, data map[string]interface{}) error {
	ctx := context.Background()

	existingProgress, err := h.db.GetReadingProgress(ctx, database.GetReadingProgressParams{
		MediaItemID: mediaItemID,
		UserID:      userID,
	})
	if err != nil && !errors.Is(err, pgx.ErrNoRows) {
		return err
	}

	percentage := 0.0
	if p, ok := data["percentage"].(float64); ok {
		percentage = p
	}

	var epubcfi pgtype.Text
	if e, ok := data["epubcfi"].(string); ok {
		epubcfi = pgtype.Text{String: e, Valid: true}
	}

	var chapter pgtype.Int4
	if c, ok := data["chapter"].(float64); ok {
		chapter = pgtype.Int4{Int32: int32(c), Valid: true}
	}

	var characterOffset pgtype.Int8
	if c, ok := data["character"].(float64); ok {
		characterOffset = pgtype.Int8{Int64: int64(c), Valid: true}
	}

	var currentPage pgtype.Int4
	var totalPages pgtype.Int4
	if err == nil {
		currentPage = existingProgress.CurrentPage
		totalPages = existingProgress.TotalPages
	}

	if p, ok := data["page"].(float64); ok {
		currentPage = pgtype.Int4{Int32: int32(p), Valid: true}
	}
	if p, ok := data["total_pages"].(float64); ok {
		totalPages = pgtype.Int4{Int32: int32(p), Valid: true}
	}

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

	return err
}

func (h *ConflictHandler) applyAnnotationResolution(mediaItemID pgtype.UUID, userID pgtype.UUID, winnerData map[string]interface{}, conflictType string) error {
	ctx := context.Background()

	dedupKey, _ := winnerData["dedup_key"].(string)
	if dedupKey == "" {
		return errors.New("missing dedup_key in winner data")
	}

	switch conflictType {
	case "annotation_highlight":
		return h.applyHighlightResolution(ctx, mediaItemID, userID, dedupKey, winnerData)
	case "annotation_bookmark":
		return h.applyBookmarkResolution(ctx, mediaItemID, userID, dedupKey, winnerData)
	case "annotation_note":
		return h.applyNoteResolution(ctx, mediaItemID, userID, dedupKey, winnerData)
	default:
		return errors.New("unknown annotation conflict type")
	}
}

func (h *ConflictHandler) applyHighlightResolution(ctx context.Context, mediaItemID pgtype.UUID, userID pgtype.UUID, dedupKey string, data map[string]interface{}) error {
	existing, err := h.db.GetMediaHighlightByDedupKey(ctx, database.GetMediaHighlightByDedupKeyParams{
		MediaItemID: mediaItemID,
		DedupKey:    pgtype.Text{String: dedupKey, Valid: true},
	})
	if err != nil {
		return err
	}

	params := database.UpdateMediaHighlightForSyncParams{
		ID:                existing.ID,
		SelectionText:     existing.SelectionText,
		StartPosition:     existing.StartPosition,
		EndPosition:       existing.EndPosition,
		Color:             existing.Color,
		NoteText:          existing.NoteText,
		PercentageStart:   existing.PercentageStart,
		PercentageEnd:     existing.PercentageEnd,
		EpubcfiStart:      existing.EpubcfiStart,
		EpubcfiEnd:        existing.EpubcfiEnd,
		ChapterReference:  existing.ChapterReference,
		LastModifiedAt:    pgtype.Timestamptz{Time: time.Now(), Valid: true},
		LastModifiedSource: pgtype.Text{String: "conflict_resolution", Valid: true},
		DeviceSyncData:     existing.DeviceSyncData,
	}

	if v, ok := data["selection_text"].(string); ok {
		params.SelectionText = v
	}
	if v, ok := data["color"].(string); ok {
		params.Color = pgtype.Text{String: v, Valid: true}
	}
	if v, ok := data["note_text"].(string); ok {
		params.NoteText = pgtype.Text{String: v, Valid: true}
	}
	if v, ok := data["start_position"].(string); ok {
		params.StartPosition = pgtype.Text{String: v, Valid: true}
	}
	if v, ok := data["end_position"].(string); ok {
		params.EndPosition = pgtype.Text{String: v, Valid: true}
	}

	_, err = h.db.UpdateMediaHighlightForSync(ctx, params)
	return err
}

func (h *ConflictHandler) applyBookmarkResolution(ctx context.Context, mediaItemID pgtype.UUID, userID pgtype.UUID, dedupKey string, data map[string]interface{}) error {
	existing, err := h.db.GetMediaBookmarkByDedupKey(ctx, database.GetMediaBookmarkByDedupKeyParams{
		MediaItemID: mediaItemID,
		DedupKey:    pgtype.Text{String: dedupKey, Valid: true},
	})
	if err != nil {
		return err
	}

	params := database.UpdateMediaBookmarkForSyncParams{
		ID:                existing.ID,
		PageNumber:        existing.PageNumber,
		ChapterNumber:     existing.ChapterNumber,
		CfiPosition:       existing.CfiPosition,
		Title:             existing.Title,
		Position:          existing.Position,
		Notes:             existing.Notes,
		PercentageLocation: existing.PercentageLocation,
		EpubcfiLocation:   existing.EpubcfiLocation,
		ChapterReference:  existing.ChapterReference,
		LastModifiedAt:    pgtype.Timestamptz{Time: time.Now(), Valid: true},
		LastModifiedSource: pgtype.Text{String: "conflict_resolution", Valid: true},
		DeviceSyncData:     existing.DeviceSyncData,
	}

	if v, ok := data["title"].(string); ok {
		params.Title = v
	}
	if v, ok := data["notes"].(string); ok {
		params.Notes = pgtype.Text{String: v, Valid: true}
	}

	_, err = h.db.UpdateMediaBookmarkForSync(ctx, params)
	return err
}

func (h *ConflictHandler) applyNoteResolution(ctx context.Context, mediaItemID pgtype.UUID, userID pgtype.UUID, dedupKey string, data map[string]interface{}) error {
	existing, err := h.db.GetMediaNoteByDedupKey(ctx, database.GetMediaNoteByDedupKeyParams{
		MediaItemID: mediaItemID,
		DedupKey:    pgtype.Text{String: dedupKey, Valid: true},
	})
	if err != nil {
		return err
	}

	params := database.UpdateMediaNoteForSyncParams{
		ID:                 existing.ID,
		Content:            existing.Content,
		Position:           existing.Position,
		PercentageLocation: existing.PercentageLocation,
		CharacterStart:     existing.CharacterStart,
		CharacterEnd:       existing.CharacterEnd,
		EpubcfiLocation:    existing.EpubcfiLocation,
		ChapterReference:   existing.ChapterReference,
		ParagraphReference: existing.ParagraphReference,
		LastModifiedAt:     pgtype.Timestamptz{Time: time.Now(), Valid: true},
		LastModifiedSource: pgtype.Text{String: "conflict_resolution", Valid: true},
		DeviceSyncData:     existing.DeviceSyncData,
	}

	if v, ok := data["content"].(string); ok {
		params.Content = v
	}
	if v, ok := data["position"].(string); ok {
		params.Position = pgtype.Text{String: v, Valid: true}
	}

	_, err = h.db.UpdateMediaNoteForSync(ctx, params)
	return err
}

func (h *ConflictHandler) notifyDevicesOfResolution(mediaItemID pgtype.UUID, data map[string]interface{}) []string {
	devices, err := h.db.ListDevicesByType(context.Background(), "koreader")
	if err != nil {
		return []string{}
	}

	synced := []string{}
	for _, device := range devices {
		if device.SyncEnabled.Bool {
			synced = append(synced, uuid.UUID(device.ID.Bytes).String())
		}
	}

	return synced
}

func (h *ConflictHandler) DeleteConflict(c *echo.Context) error {
	user := c.Get("user").(database.Users)

	conflictID, err := uuid.Parse(c.Param("id"))
	if err != nil {
		return echo.NewHTTPError(http.StatusBadRequest, "invalid conflict ID")
	}

	conflictUUID := pgtype.UUID{Bytes: [16]byte(conflictID), Valid: true}
	conflict, err := h.db.GetSyncConflict(context.Background(), conflictUUID)
	if err != nil {
		if errors.Is(err, pgx.ErrNoRows) {
			return echo.NewHTTPError(http.StatusNotFound, "conflict not found")
		}
		return echo.NewHTTPError(http.StatusInternalServerError, "failed to get conflict")
	}

	if conflict.UserID.Bytes != user.ID.Bytes {
		return echo.NewHTTPError(http.StatusForbidden, "access denied")
	}

	if err := h.db.DeleteSyncConflict(context.Background(), conflictUUID); err != nil {
		return echo.NewHTTPError(http.StatusInternalServerError, "failed to delete conflict")
	}

	return c.NoContent(http.StatusNoContent)
}

func (h *ConflictHandler) DismissAllResolved(c *echo.Context) error {
	user := c.Get("user").(database.Users)

	conflicts, err := h.db.ListConflictsByUser(context.Background(), user.ID)
	if err != nil {
		return echo.NewHTTPError(http.StatusInternalServerError, "failed to list conflicts")
	}

	deleted := 0
	for _, conflict := range conflicts {
		if conflict.ResolutionStatus.String == "user_resolved" || conflict.ResolutionStatus.String == "bulk_resolved" {
			if err := h.db.DeleteSyncConflict(context.Background(), conflict.ID); err == nil {
				deleted++
			}
		}
	}

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

type BulkResolveRequest struct {
	ConflictIDs   []string `json:"conflict_ids" validate:"required"`
	Strategy      string   `json:"strategy" validate:"required,oneof=most_recent highest_progress manual"`
	WinningSource string   `json:"winning_source,omitempty"`
}

type BulkResolveResponse struct {
	Results []ConflictResult `json:"results"`
	Total   int              `json:"total"`
	Success int              `json:"success"`
	Failed  int              `json:"failed"`
}

type ConflictResult struct {
	ConflictID string `json:"conflict_id"`
	Status     string `json:"status"`
	Error      string `json:"error,omitempty"`
	Winner     string `json:"winner,omitempty"`
}

func (h *ConflictHandler) BulkResolveConflicts(c *echo.Context) error {
	user := c.Get("user").(database.Users)

	var req BulkResolveRequest
	if err := c.Bind(&req); err != nil {
		return echo.NewHTTPError(http.StatusBadRequest, "invalid request body")
	}

	if len(req.ConflictIDs) == 0 {
		return echo.NewHTTPError(http.StatusBadRequest, "conflict_ids required")
	}

	results := make([]ConflictResult, 0, len(req.ConflictIDs))
	successCount := 0
	failedCount := 0

	for _, conflictIDStr := range req.ConflictIDs {
		conflictID, err := uuid.Parse(conflictIDStr)
		if err != nil {
			results = append(results, ConflictResult{
				ConflictID: conflictIDStr,
				Status:     "error",
				Error:      "invalid conflict ID",
			})
			failedCount++
			continue
		}

		conflictUUID := pgtype.UUID{Bytes: [16]byte(conflictID), Valid: true}
		conflict, err := h.db.GetSyncConflict(context.Background(), conflictUUID)
		if err != nil {
			results = append(results, ConflictResult{
				ConflictID: conflictIDStr,
				Status:     "error",
				Error:      "conflict not found",
			})
			failedCount++
			continue
		}

		if conflict.UserID.Bytes != user.ID.Bytes {
			results = append(results, ConflictResult{
				ConflictID: conflictIDStr,
				Status:     "error",
				Error:      "access denied",
			})
			failedCount++
			continue
		}

		var conflictData map[string]ConflictSourceData
		if err := json.Unmarshal(conflict.ConflictData, &conflictData); err != nil {
			results = append(results, ConflictResult{
				ConflictID: conflictIDStr,
				Status:     "error",
				Error:      "failed to parse conflict data",
			})
			failedCount++
			continue
		}

		var winningSource string
		var winnerData map[string]interface{}

		switch req.Strategy {
		case "most_recent":
			winningSource, winnerData = h.getMostRecentSource(conflictData)
		case "highest_progress":
			winningSource, winnerData = h.getHighestProgressSource(conflictData)
		case "manual":
			if req.WinningSource == "" {
				results = append(results, ConflictResult{
					ConflictID: conflictIDStr,
					Status:     "error",
					Error:      "winning_source required for manual strategy",
				})
				failedCount++
				continue
			}
			source, ok := conflictData[req.WinningSource]
			if !ok {
				results = append(results, ConflictResult{
					ConflictID: conflictIDStr,
					Status:     "error",
					Error:      "invalid winning source",
				})
				failedCount++
				continue
			}
			winningSource = req.WinningSource
			winnerData = source.Data
		default:
			results = append(results, ConflictResult{
				ConflictID: conflictIDStr,
				Status:     "error",
				Error:      "invalid strategy",
			})
			failedCount++
			continue
		}

		if winnerData == nil {
			results = append(results, ConflictResult{
				ConflictID: conflictIDStr,
				Status:     "error",
				Error:      "failed to determine winner",
			})
			failedCount++
			continue
		}

		if err := h.applyResolution(conflict.MediaItemID, conflict.UserID, winningSource, winnerData); err != nil {
			results = append(results, ConflictResult{
				ConflictID: conflictIDStr,
				Status:     "error",
				Error:      "failed to apply resolution",
			})
			failedCount++
			continue
		}

		resolutionData := map[string]interface{}{
			"winner":      winningSource,
			"strategy":    req.Strategy,
			"resolved_at": time.Now(),
		}
		resolutionDataJSON, _ := json.Marshal(resolutionData)

		_, err = h.db.ResolveSyncConflict(context.Background(), database.ResolveSyncConflictParams{
			ID:               conflictUUID,
			ResolutionStatus: pgtype.Text{String: "bulk_resolved", Valid: true},
			ResolutionData:   resolutionDataJSON,
			ResolvedBy:       pgtype.UUID{Bytes: user.ID.Bytes, Valid: true},
		})

		if err != nil {
			results = append(results, ConflictResult{
				ConflictID: conflictIDStr,
				Status:     "error",
				Error:      "failed to mark as resolved",
			})
			failedCount++
			continue
		}

		results = append(results, ConflictResult{
			ConflictID: conflictIDStr,
			Status:     "success",
			Winner:     winningSource,
		})
		successCount++
	}

	return c.JSON(http.StatusOK, BulkResolveResponse{
		Results: results,
		Total:   len(req.ConflictIDs),
		Success: successCount,
		Failed:  failedCount,
	})
}

func (h *ConflictHandler) getMostRecentSource(conflictData map[string]ConflictSourceData) (string, map[string]interface{}) {
	var recentSource string
	var recentTime time.Time
	var recentData map[string]interface{}

	for source, data := range conflictData {
		if data.Timestamp.After(recentTime) {
			recentTime = data.Timestamp
			recentSource = source
			recentData = data.Data
		}
	}

	return recentSource, recentData
}

func (h *ConflictHandler) getHighestProgressSource(conflictData map[string]ConflictSourceData) (string, map[string]interface{}) {
	var highestSource string
	var highestPercentage float64 = -1
	var highestData map[string]interface{}

	for source, data := range conflictData {
		if percentage, ok := data.Data["percentage"].(float64); ok {
			if percentage > highestPercentage {
				highestPercentage = percentage
				highestSource = source
				highestData = data.Data
			}
		}
	}

	return highestSource, highestData
}

func (h *ConflictHandler) applyResolution(mediaItemID pgtype.UUID, userID pgtype.UUID, winnerSource string, data map[string]interface{}) error {
	ctx := context.Background()

	existingProgress, err := h.db.GetReadingProgress(ctx, database.GetReadingProgressParams{
		MediaItemID: mediaItemID,
		UserID:      userID,
	})
	if err != nil && !errors.Is(err, pgx.ErrNoRows) {
		return err
	}

	percentage := 0.0
	if p, ok := data["percentage"].(float64); ok {
		percentage = p
	}

	var epubcfi pgtype.Text
	if e, ok := data["epubcfi"].(string); ok {
		epubcfi = pgtype.Text{String: e, Valid: true}
	}

	var chapter pgtype.Int4
	if c, ok := data["chapter"].(float64); ok {
		chapter = pgtype.Int4{Int32: int32(c), Valid: true}
	}

	var characterOffset pgtype.Int8
	if c, ok := data["character"].(float64); ok {
		characterOffset = pgtype.Int8{Int64: int64(c), Valid: true}
	}

	var currentPage pgtype.Int4
	var totalPages pgtype.Int4
	if err == nil {
		currentPage = existingProgress.CurrentPage
		totalPages = existingProgress.TotalPages
	}

	if p, ok := data["page"].(float64); ok {
		currentPage = pgtype.Int4{Int32: int32(p), Valid: true}
	}
	if p, ok := data["total_pages"].(float64); ok {
		totalPages = pgtype.Int4{Int32: int32(p), Valid: true}
	}

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

	return err
}

func (h *ConflictHandler) BulkDismissConflicts(c *echo.Context) error {
	user := c.Get("user").(database.Users)

	var req struct {
		ConflictIDs []string `json:"conflict_ids" validate:"required"`
	}
	if err := c.Bind(&req); err != nil {
		return echo.NewHTTPError(http.StatusBadRequest, "invalid request body")
	}

	if len(req.ConflictIDs) == 0 {
		return echo.NewHTTPError(http.StatusBadRequest, "conflict_ids required")
	}

	results := make([]ConflictResult, 0, len(req.ConflictIDs))
	successCount := 0
	failedCount := 0

	for _, conflictIDStr := range req.ConflictIDs {
		conflictID, err := uuid.Parse(conflictIDStr)
		if err != nil {
			results = append(results, ConflictResult{
				ConflictID: conflictIDStr,
				Status:     "error",
				Error:      "invalid conflict ID",
			})
			failedCount++
			continue
		}

		conflictUUID := pgtype.UUID{Bytes: [16]byte(conflictID), Valid: true}
		conflict, err := h.db.GetSyncConflict(context.Background(), conflictUUID)
		if err != nil {
			results = append(results, ConflictResult{
				ConflictID: conflictIDStr,
				Status:     "error",
				Error:      "conflict not found",
			})
			failedCount++
			continue
		}

		if conflict.UserID.Bytes != user.ID.Bytes {
			results = append(results, ConflictResult{
				ConflictID: conflictIDStr,
				Status:     "error",
				Error:      "access denied",
			})
			failedCount++
			continue
		}

		if err := h.db.DeleteSyncConflict(context.Background(), conflictUUID); err != nil {
			results = append(results, ConflictResult{
				ConflictID: conflictIDStr,
				Status:     "error",
				Error:      "failed to dismiss",
			})
			failedCount++
			continue
		}

		results = append(results, ConflictResult{
			ConflictID: conflictIDStr,
			Status:     "success",
		})
		successCount++
	}

	return c.JSON(http.StatusOK, BulkResolveResponse{
		Results: results,
		Total:   len(req.ConflictIDs),
		Success: successCount,
		Failed:  failedCount,
	})
}
