package handlers

import (
	"bookhoard/internal/database"
	"bookhoard/internal/services"
	"fmt"
	"net/http"
	"strconv"

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

// Book matching service (added to Handler struct in scanner.go initialization)
func (h *Handler) getMatchingService() *services.BookMatchingService {
	return services.NewBookMatchingService(h.db)
}

// BulkLinkBooksRequest represents a bulk linking request
type BulkLinkBooksRequest struct {
	Links []struct {
		UnlinkedBookID  uuid.UUID `json:"unlinked_book_id"`
		MediaItemID     uuid.UUID `json:"media_item_id"`
		ConfidenceScore float64   `json:"confidence_score"`
	} `json:"links"`
}

// AutoLinkBooksRequest represents an auto-link request
type AutoLinkBooksRequest struct {
	ConfidenceThreshold float64 `json:"confidence_threshold"`
	Limit               int     `json:"limit"`
}

// Helper function to convert float64 to pgtype.Float8
func toFloat8(f float64) pgtype.Float8 {
	var result pgtype.Float8
	result.Scan(f)
	return result
}

// QueryBooks handles POST /api/sync/books/query
func (h *Handler) QueryBooks(c *echo.Context) error {
	matchingService := h.getMatchingService()

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

	response, err := matchingService.QueryBooks(c.Request().Context(), &req)
	if err != nil {
		return c.JSON(http.StatusInternalServerError, map[string]string{
			"error": "Failed to query books",
		})
	}

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

// LinkBook handles POST /api/sync/link-book
func (h *Handler) LinkBook(c *echo.Context) error {
	matchingService := h.getMatchingService()

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

	// Get device ID from context (set by auth middleware)
	deviceIDStr := c.Param("deviceId")
	deviceID, err := uuid.Parse(deviceIDStr)
	if err != nil {
		return c.JSON(http.StatusBadRequest, map[string]string{
			"error": "Invalid device ID",
		})
	}

	alias, err := matchingService.LinkBook(c.Request().Context(), deviceID, &req)
	if err != nil {
		return c.JSON(http.StatusInternalServerError, map[string]string{
			"error": fmt.Sprintf("Failed to link book: %v", err),
		})
	}

	return c.JSON(http.StatusOK, map[string]interface{}{
		"status": "linked",
		"device_file_alias": map[string]interface{}{
			"id":               uuid.UUID(alias.ID.Bytes).String(),
			"media_item_id":    uuid.UUID(alias.MediaItemID.Bytes).String(),
			"device_id":        uuid.UUID(alias.DeviceID.Bytes).String(),
			"file_path":        alias.FilePath,
			"file_sha256":      alias.FileSha256.String,
			"confidence_score": alias.ConfidenceScore.Float64,
		},
	})
}

// GetUnlinkedBooks handles GET /api/sync/unlinked-books
func (h *Handler) GetUnlinkedBooks(c *echo.Context) error {
	matchingService := h.getMatchingService()

	deviceIDStr := c.Param("deviceId")
	deviceID, err := uuid.Parse(deviceIDStr)
	if err != nil {
		return c.JSON(http.StatusBadRequest, map[string]string{
			"error": "Invalid device ID",
		})
	}

	unlinked, err := matchingService.GetUnlinkedBooks(c.Request().Context(), deviceID)
	if err != nil {
		return c.JSON(http.StatusInternalServerError, map[string]string{
			"error": "Failed to get unlinked books",
		})
	}

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

// GetDeviceFileAliases handles GET /api/devices/:id/file-aliases
func (h *Handler) GetDeviceFileAliases(c *echo.Context) error {
	deviceIDStr := c.Param("id")
	deviceID, err := uuid.Parse(deviceIDStr)
	if err != nil {
		return c.JSON(http.StatusBadRequest, map[string]string{
			"error": "Invalid device ID",
		})
	}

	aliases, err := h.db.GetDeviceFileAliasesByDevice(c.Request().Context(), pgtype.UUID{Bytes: deviceID, Valid: true})
	if err != nil {
		return c.JSON(http.StatusInternalServerError, map[string]string{
			"error": "Failed to get file aliases",
		})
	}

	// Convert to response format
	type AliasResponse struct {
		ID              string  `json:"id"`
		MediaItemID     string  `json:"media_item_id"`
		FilePath        string  `json:"file_path"`
		FileSHA256      string  `json:"file_sha256"`
		ConfidenceScore float64 `json:"confidence_score"`
		LastSeenAt      string  `json:"last_seen_at"`
	}

	response := make([]AliasResponse, len(aliases))
	for i, alias := range aliases {
		response[i] = AliasResponse{
			ID:              uuid.UUID(alias.ID.Bytes).String(),
			MediaItemID:     uuid.UUID(alias.MediaItemID.Bytes).String(),
			FilePath:        alias.FilePath,
			FileSHA256:      alias.FileSha256.String,
			ConfidenceScore: alias.ConfidenceScore.Float64,
			LastSeenAt:      alias.LastSeenAt.Time.String(),
		}
	}

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

// BulkLinkBooks handles POST /api/sync/bulk-link-books
func (h *Handler) BulkLinkBooks(c *echo.Context) error {
	ctx := c.Request().Context()

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

	results := make([]map[string]interface{}, 0, len(req.Links))

	for _, link := range req.Links {
		unlinkedBook, err := h.db.GetUnlinkedBookByID(ctx, pgtype.UUID{Bytes: link.UnlinkedBookID, Valid: true})
		if err != nil {
			results = append(results, map[string]interface{}{
				"unlinked_book_id": link.UnlinkedBookID.String(),
				"status":           "error",
				"error":            "Unlinked book not found",
			})
			continue
		}

		_, err = h.db.CreateDeviceFileAlias(ctx, database.CreateDeviceFileAliasParams{
			MediaItemID:     pgtype.UUID{Bytes: link.MediaItemID, Valid: true},
			DeviceID:        unlinkedBook.DeviceID,
			FilePath:        unlinkedBook.FilePath.String,
			FileSha256:      pgtype.Text{String: "", Valid: false}, // SHA256 not stored in unlinked_books
			ConfidenceScore: toFloat8(link.ConfidenceScore),
		})

		if err != nil {
			results = append(results, map[string]interface{}{
				"unlinked_book_id": link.UnlinkedBookID.String(),
				"status":           "error",
				"error":            err.Error(),
			})
			continue
		}

		_, err = h.db.LinkUnlinkedBook(ctx, database.LinkUnlinkedBookParams{
			ID:              pgtype.UUID{Bytes: link.UnlinkedBookID, Valid: true},
			MediaItemID:     pgtype.UUID{Bytes: link.MediaItemID, Valid: true},
			ConfidenceScore: toFloat8(link.ConfidenceScore),
		})

		if err != nil {
			results = append(results, map[string]interface{}{
				"unlinked_book_id": link.UnlinkedBookID.String(),
				"status":           "warning",
				"error":            "Linked but failed to mark as resolved",
			})
			continue
		}

		results = append(results, map[string]interface{}{
			"unlinked_book_id": link.UnlinkedBookID.String(),
			"status":           "success",
			"media_item_id":    link.MediaItemID.String(),
		})
	}

	successfulCount := 0
	for _, r := range results {
		if r["status"] == "success" {
			successfulCount++
		}
	}

	return c.JSON(http.StatusOK, map[string]interface{}{
		"results":    results,
		"total":      len(req.Links),
		"successful": successfulCount,
		"failed":     len(req.Links) - successfulCount,
	})
}

// AutoLinkBooks handles POST /api/sync/auto-link-books
func (h *Handler) AutoLinkBooks(c *echo.Context) error {
	ctx := c.Request().Context()
	matchingService := h.getMatchingService()

	var req AutoLinkBooksRequest
	if err := c.Bind(&req); err != nil {
		req.ConfidenceThreshold = 0.8
		req.Limit = 50
	}

	if req.ConfidenceThreshold == 0 {
		req.ConfidenceThreshold = 0.8
	}
	if req.Limit == 0 {
		req.Limit = 50
	}

	offset := 0
	unlinked, err := h.db.ListUnresolvedUnlinkedBooks(ctx, database.ListUnresolvedUnlinkedBooksParams{
		Limit:  int32(req.Limit),
		Offset: int32(offset),
	})
	if err != nil {
		return c.JSON(http.StatusInternalServerError, map[string]string{
			"error": "Failed to get unlinked books",
		})
	}

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

	for _, book := range unlinked {
		queryReq := &services.BookQueryRequest{
			Title: book.Title.String,
		}

		match, err := matchingService.QueryBooks(ctx, queryReq)
		if err != nil {
			continue
		}

		if len(match.Matches) > 0 && match.Matches[0].Confidence >= req.ConfidenceThreshold {
			bestMatch := match.Matches[0]

			_, err := h.db.CreateDeviceFileAlias(ctx, database.CreateDeviceFileAliasParams{
				MediaItemID:     pgtype.UUID{Bytes: bestMatch.BookhoardUUID, Valid: true},
				DeviceID:        book.DeviceID,
				FilePath:        book.FilePath.String,
				FileSha256:      pgtype.Text{String: "", Valid: false},
				ConfidenceScore: toFloat8(bestMatch.Confidence),
			})

			if err == nil {
				h.db.LinkUnlinkedBook(ctx, database.LinkUnlinkedBookParams{
					ID:              book.ID,
					MediaItemID:     pgtype.UUID{Bytes: bestMatch.BookhoardUUID, Valid: true},
					ConfidenceScore: toFloat8(bestMatch.Confidence),
				})

				results = append(results, map[string]interface{}{
					"unlinked_book_id":      uuid.UUID(book.ID.Bytes).String(),
					"title":                 book.Title.String,
					"matched_media_item_id": bestMatch.BookhoardUUID.String(),
					"confidence":            bestMatch.Confidence,
					"match_method":          bestMatch.MatchMethod,
				})
			}
		}
	}

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

// GetUnlinkedBookSuggestions handles GET /api/sync/unlinked-books/:id/suggestions
func (h *Handler) GetUnlinkedBookSuggestions(c *echo.Context) error {
	ctx := c.Request().Context()
	matchingService := h.getMatchingService()

	unlinkedBookID := c.Param("id")
	unlinkedUUID, err := uuid.Parse(unlinkedBookID)
	if err != nil {
		return c.JSON(http.StatusBadRequest, map[string]string{
			"error": "Invalid unlinked book ID",
		})
	}

	unlinked, err := h.db.GetUnlinkedBookByID(ctx, pgtype.UUID{Bytes: unlinkedUUID, Valid: true})
	if err != nil {
		return c.JSON(http.StatusNotFound, map[string]string{
			"error": "Unlinked book not found",
		})
	}

	matches, err := matchingService.QueryBooks(ctx, &services.BookQueryRequest{
		Title: unlinked.Title.String,
	})

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

	return c.JSON(http.StatusOK, map[string]interface{}{
		"unlinked_book_id":  unlinkedBookID,
		"title_from_device": unlinked.Title.String,
		"sha256":            "",
		"suggestions":       matches.Matches,
		"total_suggestions": len(matches.Matches),
		"action":            matches.Action,
	})
}

// CreateDeviceFileAlias handles POST /api/devices/:id/file-aliases
func (h *Handler) CreateDeviceFileAlias(c *echo.Context) error {
	deviceIDStr := c.Param("id")
	deviceID, err := uuid.Parse(deviceIDStr)
	if err != nil {
		return c.JSON(http.StatusBadRequest, map[string]string{
			"error": "Invalid device ID",
		})
	}

	var req struct {
		MediaItemID     string  `json:"media_item_id"`
		FilePath        string  `json:"file_path"`
		FileSHA256      string  `json:"file_sha256"`
		ConfidenceScore float64 `json:"confidence_score"`
	}

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

	mediaItemID, err := uuid.Parse(req.MediaItemID)
	if err != nil {
		return c.JSON(http.StatusBadRequest, map[string]string{
			"error": "Invalid media item ID",
		})
	}

	alias, err := h.db.CreateDeviceFileAlias(c.Request().Context(), database.CreateDeviceFileAliasParams{
		MediaItemID:     pgtype.UUID{Bytes: mediaItemID, Valid: true},
		DeviceID:        pgtype.UUID{Bytes: deviceID, Valid: true},
		FilePath:        req.FilePath,
		FileSha256:      pgtype.Text{String: req.FileSHA256, Valid: req.FileSHA256 != ""},
		ConfidenceScore: pgtype.Float8{Float64: req.ConfidenceScore, Valid: true},
	})

	if err != nil {
		return c.JSON(http.StatusInternalServerError, map[string]string{
			"error": fmt.Sprintf("Failed to create file alias: %v", err),
		})
	}

	return c.JSON(http.StatusCreated, map[string]interface{}{
		"id":               uuid.UUID(alias.ID.Bytes).String(),
		"media_item_id":    uuid.UUID(alias.MediaItemID.Bytes).String(),
		"device_id":        deviceIDStr,
		"file_path":        alias.FilePath,
		"file_sha256":      alias.FileSha256.String,
		"confidence_score": alias.ConfidenceScore.Float64,
		"last_seen_at":     alias.LastSeenAt.Time.String(),
	})
}

// UpdateDeviceFileAlias handles PUT /api/devices/:id/file-aliases/:aliasId
func (h *Handler) UpdateDeviceFileAlias(c *echo.Context) error {
	aliasIDStr := c.Param("aliasId")
	aliasID, err := uuid.Parse(aliasIDStr)
	if err != nil {
		return c.JSON(http.StatusBadRequest, map[string]string{
			"error": "Invalid alias ID",
		})
	}

	var req struct {
		MediaItemID     *string  `json:"media_item_id"`
		FileSHA256      *string  `json:"file_sha256"`
		ConfidenceScore *float64 `json:"confidence_score"`
	}

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

	// Build update parameters
	var mediaItemID pgtype.UUID
	if req.MediaItemID != nil {
		parsedID, err := uuid.Parse(*req.MediaItemID)
		if err != nil {
			return c.JSON(http.StatusBadRequest, map[string]string{
				"error": "Invalid media item ID",
			})
		}
		mediaItemID = pgtype.UUID{Bytes: parsedID, Valid: true}
	}

	var fileSHA256 pgtype.Text
	if req.FileSHA256 != nil {
		fileSHA256 = pgtype.Text{String: *req.FileSHA256, Valid: true}
	}

	var confidenceScore pgtype.Float8
	if req.ConfidenceScore != nil {
		confidenceScore = pgtype.Float8{Float64: *req.ConfidenceScore, Valid: true}
	}

	alias, err := h.db.UpdateDeviceFileAlias(c.Request().Context(), database.UpdateDeviceFileAliasParams{
		ID:              pgtype.UUID{Bytes: aliasID, Valid: true},
		MediaItemID:     mediaItemID,
		FileSha256:      fileSHA256,
		ConfidenceScore: confidenceScore,
	})

	if err != nil {
		return c.JSON(http.StatusInternalServerError, map[string]string{
			"error": fmt.Sprintf("Failed to update file alias: %v", err),
		})
	}

	return c.JSON(http.StatusOK, map[string]interface{}{
		"id":               uuid.UUID(alias.ID.Bytes).String(),
		"media_item_id":    uuid.UUID(alias.MediaItemID.Bytes).String(),
		"file_path":        alias.FilePath,
		"file_sha256":      alias.FileSha256.String,
		"confidence_score": alias.ConfidenceScore.Float64,
		"last_seen_at":     alias.LastSeenAt.Time.String(),
	})
}

// DeleteDeviceFileAlias handles DELETE /api/devices/:id/file-aliases/:aliasId
func (h *Handler) DeleteDeviceFileAlias(c *echo.Context) error {
	aliasIDStr := c.Param("aliasId")
	aliasID, err := uuid.Parse(aliasIDStr)
	if err != nil {
		return c.JSON(http.StatusBadRequest, map[string]string{
			"error": "Invalid alias ID",
		})
	}

	err = h.db.DeleteDeviceFileAlias(c.Request().Context(), pgtype.UUID{Bytes: aliasID, Valid: true})
	if err != nil {
		return c.JSON(http.StatusInternalServerError, map[string]string{
			"error": "Failed to delete file alias",
		})
	}

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

// GetBookMatches handles GET /api/books/match
func (h *Handler) GetBookMatches(c *echo.Context) error {
	matchingService := h.getMatchingService()

	// Get query parameters
	identifiers := c.QueryParams()["identifier"]
	sha256 := c.QueryParam("sha256")
	title := c.QueryParam("title")
	author := c.QueryParam("author")
	fileSizeStr := c.QueryParam("file_size")

	var fileSize int64
	if fileSizeStr != "" {
		size, err := strconv.ParseInt(fileSizeStr, 10, 64)
		if err != nil {
			return c.JSON(http.StatusBadRequest, map[string]string{
				"error": "Invalid file_size parameter",
			})
		}
		fileSize = size
	}

	req := &services.BookQueryRequest{
		Identifiers: identifiers,
		SHA256:      sha256,
		Title:       title,
		Author:      author,
		FileSize:    fileSize,
	}

	response, err := matchingService.QueryBooks(c.Request().Context(), req)
	if err != nil {
		return c.JSON(http.StatusInternalServerError, map[string]string{
			"error": "Failed to query books",
		})
	}

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