package handlers

import (
	"bookhoard/internal/database"
	"fmt"
	"net/http"
	"time"

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

// HashConflictsHandler serves the admin Hash Conflicts page API: listing
// content-duplicate groups (same library + SHA-256 at different paths) and
// resolving them by keeping every copy or merging all but one.
type HashConflictsHandler struct {
	db *database.Queries
}

func NewHashConflictsHandler(db *database.Queries) *HashConflictsHandler {
	return &HashConflictsHandler{db: db}
}

// HashConflictItem is one copy in a conflict group, hydrated with per-item
// user-data counts so the admin can make an informed keep/merge choice.
type HashConflictItem struct {
	ID              uuid.UUID `json:"id"`
	Title           string    `json:"title"`
	Author          string    `json:"author,omitempty"`
	FilePath        string    `json:"file_path"`
	FileSize        int64     `json:"file_size,omitempty"`
	CreatedAt       string    `json:"created_at"`
	ProgressCount   int64     `json:"progress_count"`
	HighlightCount  int64     `json:"highlight_count"`
	BookmarkCount   int64     `json:"bookmark_count"`
	NoteCount       int64     `json:"note_count"`
	CollectionCount int64     `json:"collection_count"`
}

// HashConflictResponse is one pending conflict group.
type HashConflictResponse struct {
	ID          string             `json:"id"`
	LibraryID   string             `json:"library_id"`
	LibraryName string             `json:"library_name"`
	SHA256      string             `json:"sha256"`
	CreatedAt   string             `json:"created_at"`
	Items       []HashConflictItem `json:"items"`
}

// ListHashConflicts returns all pending hash conflict groups with their member
// items and usage counts.
// GET /api/admin/hash-conflicts
func (h *HashConflictsHandler) ListHashConflicts(c *echo.Context) error {
	ctx := c.Request().Context()

	pending, err := h.db.ListPendingHashConflicts(ctx)
	if err != nil {
		return c.JSON(http.StatusInternalServerError, map[string]string{
			"error": "failed to list hash conflicts",
		})
	}

	conflicts := make([]HashConflictResponse, 0, len(pending))
	for _, p := range pending {
		resp := HashConflictResponse{
			ID:          uuid.UUID(p.ID.Bytes).String(),
			LibraryID:   uuid.UUID(p.LibraryID.Bytes).String(),
			LibraryName: p.LibraryName,
			SHA256:      p.FileSha256,
			CreatedAt:   p.CreatedAt.Time.Format(time.RFC3339),
			Items:       []HashConflictItem{},
		}
		items, err := h.db.ListMediaItemsBySHA256AndLibrary(ctx, database.ListMediaItemsBySHA256AndLibraryParams{
			FileSha256: pgtype.Text{String: p.FileSha256, Valid: true},
			LibraryID:  p.LibraryID,
		})
		if err != nil {
			continue
		}

		for _, mi := range items {
			counts, err := h.db.GetMediaItemUsageCounts(ctx, mi.ID)
			if err != nil {
				counts = database.GetMediaItemUsageCountsRow{}
			}
			resp.Items = append(resp.Items, HashConflictItem{
				ID:              uuid.UUID(mi.ID.Bytes),
				Title:           mi.Title,
				Author:          mi.Author.String,
				FilePath:        mi.FilePath,
				FileSize:        mi.FileSize.Int64,
				CreatedAt:       mi.CreatedAt.Time.Format(time.RFC3339),
				ProgressCount:   counts.ProgressCount,
				HighlightCount:  counts.HighlightsCount,
				BookmarkCount:   counts.BookmarksCount,
				NoteCount:       counts.NotesCount,
				CollectionCount: counts.CollectionsCount,
			})
		}

		conflicts = append(conflicts, resp)
	}

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

// ResolveHashConflict resolves one conflict group.
//
// Form/JSON fields:
//   - action=keep_all              both copies are intentional; dismiss
//   - action=keep&keep_uuid=<uuid> merge every other copy's child rows into the
//     kept item (progress, highlights, bookmarks,
//     notes, collections, ...) and delete the losers
//
// POST /api/admin/hash-conflicts/:id/resolve
func (h *HashConflictsHandler) ResolveHashConflict(c *echo.Context) error {
	ctx := c.Request().Context()

	conflictID, err := uuid.Parse(c.Param("id"))
	if err != nil {
		return c.JSON(http.StatusBadRequest, map[string]string{"error": "invalid conflict ID"})
	}
	pgConflictID := pgtype.UUID{Bytes: conflictID, Valid: true}

	conflict, err := h.db.GetHashConflict(ctx, pgConflictID)
	if err != nil {
		return c.JSON(http.StatusNotFound, map[string]string{"error": "conflict not found"})
	}
	if conflict.Status != "pending" {
		return c.JSON(http.StatusConflict, map[string]string{"error": "conflict already resolved"})
	}

	action := c.FormValue("action")
	keepUUIDStr := c.FormValue("keep_uuid")
	if action == "" {
		// Also accept a JSON body (htmx sends form-encoded, API clients may send JSON)
		var body struct {
			Action   string `json:"action"`
			KeepUUID string `json:"keep_uuid"`
		}
		if err := c.Bind(&body); err == nil && body.Action != "" {
			action = body.Action
			if keepUUIDStr == "" {
				keepUUIDStr = body.KeepUUID
			}
		}
	}

	var pgUserID pgtype.UUID
	if userID, ok := c.Get("user_id").(string); ok && userID != "" {
		if u, err := uuid.Parse(userID); err == nil {
			pgUserID = pgtype.UUID{Bytes: u, Valid: true}
		}
	}

	switch action {
	case "keep_all":
		if err := h.db.ResolveHashConflict(ctx, database.ResolveHashConflictParams{
			ID:         pgConflictID,
			Resolution: pgtype.Text{String: "keep_all", Valid: true},
			ResolvedBy: pgUserID,
		}); err != nil {
			return c.JSON(http.StatusInternalServerError, map[string]string{"error": "failed to resolve conflict"})
		}
		return renderResolved(c, "All copies kept.")

	case "keep":
		keepUUID, err := uuid.Parse(keepUUIDStr)
		if err != nil {
			return c.JSON(http.StatusBadRequest, map[string]string{"error": "keep_uuid is required for action=keep"})
		}
		pgKeepUUID := pgtype.UUID{Bytes: keepUUID, Valid: true}

		// Validate the kept item belongs to this conflict group.
		items, err := h.db.ListMediaItemsBySHA256AndLibrary(ctx, database.ListMediaItemsBySHA256AndLibraryParams{
			FileSha256: pgtype.Text{String: conflict.FileSha256, Valid: true},
			LibraryID:  conflict.LibraryID,
		})
		if err != nil {
			return c.JSON(http.StatusInternalServerError, map[string]string{"error": "failed to load conflict group"})
		}

		keepValid := false
		for _, mi := range items {
			if mi.ID.Bytes == pgKeepUUID.Bytes {
				keepValid = true
				break
			}
		}
		if !keepValid {
			return c.JSON(http.StatusBadRequest, map[string]string{"error": "keep_uuid is not part of this conflict"})
		}

		merged := 0
		for _, mi := range items {
			if mi.ID.Bytes == pgKeepUUID.Bytes {
				continue
			}
			if err := h.db.ReparentMediaItemChildren(ctx, database.ReparentMediaItemChildrenParams{
				Column1: pgKeepUUID,
				Column2: mi.ID,
			}); err != nil {
				return c.JSON(http.StatusInternalServerError, map[string]string{
					"error": fmt.Sprintf("failed to merge %q: %v", mi.FilePath, err),
				})
			}
			if err := h.db.DeleteMediaItem(ctx, mi.ID); err != nil {
				return c.JSON(http.StatusInternalServerError, map[string]string{
					"error": fmt.Sprintf("failed to delete %q: %v", mi.FilePath, err),
				})
			}
			merged++
		}

		if err := h.db.ResolveHashConflict(ctx, database.ResolveHashConflictParams{
			ID:         pgConflictID,
			Resolution: pgtype.Text{String: "kept:" + keepUUID.String(), Valid: true},
			ResolvedBy: pgUserID,
		}); err != nil {
			return c.JSON(http.StatusInternalServerError, map[string]string{"error": "failed to resolve conflict"})
		}

		return renderResolved(c, fmt.Sprintf("Merged %d duplicate cop%s - all reading data preserved.",
			merged, map[bool]string{true: "y", false: "ies"}[merged == 1]))

	default:
		return c.JSON(http.StatusBadRequest, map[string]string{"error": "action must be 'keep_all' or 'keep'"})
	}
}

// renderResolved returns the htmx fragment swapped in place of a conflict card.
// Built inline (rather than via the templates package) because templates
// imports handlers and a back-import would be a cycle.
func renderResolved(c *echo.Context, message string) error {
	html := fmt.Sprintf(`
<div class="card p-6 flex items-center gap-3">
	<span class="grid place-items-center h-10 w-10 rounded-xl shrink-0"
		style="background-color: var(--accent-muted); color: var(--accent);">
		<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor"
			stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="h-5 w-5" aria-hidden="true">
			<path d="M22 11.08V12a10 10 0 1 1-5.93-9.14"></path>
			<polyline points="22 4 12 14.01 9 11.01"></polyline>
		</svg>
	</span>
	<div>
		<p class="font-medium" style="color: var(--text-primary);">Conflict resolved</p>
		<p class="text-sm" style="color: var(--text-secondary);">%s</p>
	</div>
</div>`, message)
	return c.HTML(http.StatusOK, html)
}
