package handlers

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

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

type ReaderHandler struct {
	db             *database.Queries
	libraryService *services.LibraryService
	readerService  *services.ReaderService
	worker         *services.Worker
}

func NewReaderHandler(
	db *database.Queries,
	libraryService *services.LibraryService,
	readerService *services.ReaderService,
	worker ...*services.Worker,
) *ReaderHandler {
	rh := &ReaderHandler{
		db:             db,
		libraryService: libraryService,
		readerService:  readerService,
	}
	if len(worker) > 0 && worker[0] != nil {
		rh.worker = worker[0]
	}
	return rh
}

// ReaderMetadata contains information needed to render the reader
type ReaderMetadata struct {
	MediaItemID    string `json:"media_item_id"`
	Title          string `json:"title"`
	Author         string `json:"author"`
	CoverImagePath string `json:"cover_image_path"`
	LibraryType    string `json:"library_type"`
	MimeType       string `json:"mime_type"`
	FilePath       string `json:"file_path"`
	TotalPages     int    `json:"total_pages"`
	ChapterCount   int    `json:"chapter_count"`
}

// ShowReader renders the reader page (SSR)
func (h *ReaderHandler) ShowReader(c *echo.Context) error {
	mediaItemID := c.Param("mediaItemId")
	parsedUUID, err := uuid.Parse(mediaItemID)
	if err != nil {
		return c.JSON(http.StatusBadRequest, map[string]string{"error": "Invalid media item ID"})
	}

	// Get media item
	mediaItem, err := h.db.GetMediaItem(c.Request().Context(), pgtype.UUID{Bytes: parsedUUID, Valid: true})
	if err != nil {
		if errors.Is(err, pgx.ErrNoRows) {
			return c.JSON(http.StatusNotFound, map[string]string{"error": "Media item not found"})
		}
		return c.JSON(http.StatusInternalServerError, map[string]string{"error": "Failed to fetch media item"})
	}

	// Get user from context (set by JWT middleware)
	user := c.Get("user")
	if user == nil {
		return c.JSON(http.StatusUnauthorized, map[string]string{"error": "User not authenticated"})
	}
	userData := user.(database.Users)

	// Get user's visible libraries
	libraries, err := h.libraryService.GetUserVisibleLibraries(c.Request().Context(), userData.ID)
	if err != nil {
		return c.JSON(http.StatusInternalServerError, map[string]string{"error": "Failed to check library access"})
	}
	// Check if media item's library is in visible list
	visible := false
	for _, lib := range libraries {
		if lib.ID == mediaItem.LibraryID {
			visible = true
			break
		}
	}
	if !visible {
		return c.JSON(http.StatusForbidden, map[string]string{"error": "Access denied to this library"})
	}

	// Get library with type information for panel detection decision
	libraryWithType, err := h.db.GetLibraryWithType(c.Request().Context(), mediaItem.LibraryID)
	if err != nil {
		return c.JSON(http.StatusInternalServerError, map[string]string{"error": "Failed to get library info"})
	}

	// Determine if panel detection should be enabled
	enablePanelDetection := shouldEnablePanelDetection(libraryWithType.TypeName, mediaItem.FormatGroup)

	// Get reading progress
	var progress database.ReadingProgress
	progress, err = h.db.GetReadingProgress(c.Request().Context(), database.GetReadingProgressParams{
		MediaItemID: pgtype.UUID{Bytes: parsedUUID, Valid: true},
		UserID:      userData.ID,
	})
	if err != nil && !errors.Is(err, pgx.ErrNoRows) {
		progress = database.ReadingProgress{}
	}

	// Get bookmarks
	bookmarks, _ := h.db.GetMediaBookmarks(c.Request().Context(), database.GetMediaBookmarksParams{
		MediaItemID: pgtype.UUID{Bytes: parsedUUID, Valid: true},
		UserID:      userData.ID,
	})

	// Return JSON response instead of rendering template
	return c.JSON(http.StatusOK, map[string]interface{}{
		"media_item_id":          mediaItemID,
		"title":                  mediaItem.Title,
		"author":                 textToString(mediaItem.Author),
		"cover_image_path":       textToString(mediaItem.CoverImagePath),
		"library_type":           mediaItem.FormatGroup,
		"mime_type":              textToString(mediaItem.MimeType),
		"file_path":              mediaItem.FilePath,
		"total_pages":            mediaItem.PageCount,
		"chapter_count":          mediaItem.ChapterCount,
		"progress":               progress,
		"bookmarks":              bookmarks,
		"enable_panel_detection": enablePanelDetection,
		"format_group":           mediaItem.FormatGroup,
		"manga_type":             mediaItem.MangaType,
		"reading_direction":      mediaItem.ReadingDirection,
	})
}

// GetPage returns a specific page for lazy loading
func (h *ReaderHandler) GetPage(c *echo.Context) error {
	mediaItemID := c.Param("mediaItemId")
	pageNumber := c.Param("pageNumber")

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

	page, err := strconv.Atoi(pageNumber)
	if err != nil || page < 1 {
		return c.JSON(http.StatusBadRequest, map[string]string{"error": "Invalid page number"})
	}

	// Get media item
	mediaItem, err := h.db.GetMediaItem(c.Request().Context(), pgtype.UUID{Bytes: parsedUUID, Valid: true})
	if err != nil {
		return c.JSON(http.StatusNotFound, map[string]string{"error": "Media item not found"})
	}

	// Get user from context (set by JWT middleware)
	user := c.Get("user")
	if user == nil {
		return c.JSON(http.StatusUnauthorized, map[string]string{"error": "User not authenticated"})
	}
	userData := user.(database.Users)

	// Get user's visible libraries
	libraries, err := h.libraryService.GetUserVisibleLibraries(c.Request().Context(), userData.ID)
	if err != nil {
		return c.JSON(http.StatusInternalServerError, map[string]string{"error": "Failed to check library access"})
	}
	// Check if media item's library is in visible list
	visible := false
	for _, lib := range libraries {
		if lib.ID == mediaItem.LibraryID {
			visible = true
			break
		}
	}
	if !visible {
		return c.JSON(http.StatusForbidden, map[string]string{"error": "Access denied to this library"})
	}

	// Resolve full file path
	fullPath, err := h.getFullFilePath(c.Request().Context(), mediaItem.LibraryID, mediaItem.FilePath)
	if err != nil {
		return c.JSON(http.StatusNotFound, map[string]string{"error": "File not found"})
	}

	// Get requested format
	format := c.QueryParam("format")
	if format == "" {
		format = "html"
	}

	// Extract page content based on format
	content, err := h.extractPageContent(c.Request().Context(), &mediaItem, page, format, fullPath)
	if err != nil {
		return c.JSON(http.StatusInternalServerError, map[string]string{"error": err.Error()})
	}

	return c.JSON(http.StatusOK, map[string]interface{}{
		"content":       content,
		"page_number":   page,
		"total_pages":   mediaItem.PageCount,
		"media_item_id": mediaItemID,
	})
}

// GetChapters returns chapter metadata
func (h *ReaderHandler) GetChapters(c *echo.Context) error {
	mediaItemID := c.Param("mediaItemId")
	parsedUUID, err := uuid.Parse(mediaItemID)
	if err != nil {
		return c.JSON(http.StatusBadRequest, map[string]string{"error": "Invalid media item ID"})
	}

	// Use reader service to detect chapters
	chapters, err := h.readerService.DetectChapters(c.Request().Context(), parsedUUID)
	if err != nil {
		return c.JSON(http.StatusInternalServerError, map[string]string{"error": "Failed to detect chapters"})
	}

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

// GetPanels returns panel detection data for comics
func (h *ReaderHandler) GetPanels(c *echo.Context) error {
	mediaItemID := c.Param("mediaItemId")
	pageNumber := c.Param("pageNumber")

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

	page, err := strconv.Atoi(pageNumber)
	if err != nil || page < 1 {
		return c.JSON(http.StatusBadRequest, map[string]string{"error": "Invalid page number"})
	}

	// Get detection method from query
	method := c.QueryParam("method")
	if method == "" {
		method = "grid"
	}

	// Use reader service to detect panels
	panels, err := h.readerService.DetectPanels(c.Request().Context(), parsedUUID, page, method)
	if err != nil {
		return c.JSON(http.StatusInternalServerError, map[string]string{"error": "Failed to detect panels"})
	}

	return c.JSON(http.StatusOK, map[string]interface{}{
		"page_number":      page,
		"detection_method": method,
		"panels":           panels,
	})
}

// UpdatePanels allows manual panel override
func (h *ReaderHandler) UpdatePanels(c *echo.Context) error {
	mediaItemID := c.Param("mediaItemId")
	pageNumber := c.Param("pageNumber")

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

	page, err := strconv.Atoi(pageNumber)
	if err != nil || page < 1 {
		return c.JSON(http.StatusBadRequest, map[string]string{"error": "Invalid page number"})
	}

	// Parse request body
	var req struct {
		Panels          []services.Panel `json:"panels"`
		DetectionMethod string           `json:"detection_method"`
	}

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

	// Store manual panel override in database
	// This would require implementing UpsertPanelData in database/queries.sql
	_ = parsedUUID
	_ = page
	_ = req

	return c.JSON(http.StatusOK, map[string]interface{}{
		"success": true,
		"message": "Panels updated successfully",
	})
}

// GetReadingSpeed retrieves reading speed statistics
func (h *ReaderHandler) GetReadingSpeed(c *echo.Context) error {
	mediaItemID := c.Param("mediaItemId")
	parsedUUID, err := uuid.Parse(mediaItemID)
	if err != nil {
		return c.JSON(http.StatusBadRequest, map[string]string{"error": "Invalid media item ID"})
	}

	user := c.Get("user").(database.Users)

	// Get reading speed from database
	speed, err := h.db.GetReadingSpeed(c.Request().Context(), database.GetReadingSpeedParams{
		UserID:      user.ID,
		MediaItemID: pgtype.UUID{Bytes: parsedUUID, Valid: true},
	})

	if err != nil {
		if errors.Is(err, pgx.ErrNoRows) {
			// Return zero values if no reading has occurred
			return c.JSON(http.StatusOK, map[string]interface{}{
				"words_per_minute":      0,
				"pages_per_minute":      0,
				"pages_read":            0,
				"total_reading_minutes": 0,
				"last_read_at":          nil,
			})
		}
		return c.JSON(http.StatusInternalServerError, map[string]string{"error": "Failed to fetch reading speed"})
	}

	return c.JSON(http.StatusOK, map[string]interface{}{
		"words_per_minute":      speed.PagesPerMinute.Float32 * 250, // Estimate WPM
		"pages_per_minute":      speed.PagesPerMinute.Float32,
		"pages_read":            speed.PagesRead,
		"total_reading_minutes": speed.TotalReadingMinutes.Float32,
		"last_read_at":          speed.LastReadAt.Time,
	})
}

// UpdateReadingSpeed updates reading speed statistics
func (h *ReaderHandler) UpdateReadingSpeed(c *echo.Context) error {
	mediaItemID := c.Param("mediaItemId")
	parsedUUID, err := uuid.Parse(mediaItemID)
	if err != nil {
		return c.JSON(http.StatusBadRequest, map[string]string{"error": "Invalid media item ID"})
	}

	user := c.Get("user").(database.Users)

	// Parse request body
	var req struct {
		PagesRead        int     `json:"pages_read"`
		TimeSpentMinutes float32 `json:"time_spent_minutes"`
	}

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

	// Update reading speed using service
	err = h.readerService.CalculateReadingSpeed(
		c.Request().Context(),
		user.ID.Bytes,
		parsedUUID,
		req.PagesRead,
		req.TimeSpentMinutes,
	)

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

	// Calculate and return updated statistics
	pagesPerMinute := float32(req.PagesRead) / req.TimeSpentMinutes
	wordsPerMinute := pagesPerMinute * 250 // Estimate

	return c.JSON(http.StatusOK, map[string]interface{}{
		"success":          true,
		"words_per_minute": wordsPerMinute,
		"pages_per_minute": pagesPerMinute,
	})
}

// GetPDFOutline returns PDF outline/TOC
func (h *ReaderHandler) GetPDFOutline(c *echo.Context) error {
	mediaItemID := c.Param("mediaItemId")
	parsedUUID, err := uuid.Parse(mediaItemID)
	if err != nil {
		return c.JSON(http.StatusBadRequest, map[string]string{"error": "Invalid media item ID"})
	}

	// Get media item
	mediaItem, err := h.db.GetMediaItem(c.Request().Context(), pgtype.UUID{Bytes: parsedUUID, Valid: true})
	if err != nil {
		return c.JSON(http.StatusNotFound, map[string]string{"error": "Media item not found"})
	}

	// Only PDFs have outlines
	if mediaItem.FormatGroup != "pdf" {
		return c.JSON(http.StatusBadRequest, map[string]string{"error": "Not a PDF file"})
	}

	// Extract PDF outline using pdfcpu
	fullPath, err := h.getFullFilePath(c.Request().Context(), mediaItem.LibraryID, mediaItem.FilePath)
	if err != nil {
		return c.JSON(http.StatusNotFound, map[string]string{"error": "File not found"})
	}

	// Use pdfcpu to extract outline
	outline := h.extractPDFOutline(fullPath)

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

// GetPDFThumbnail returns a thumbnail for PDF mini-map
func (h *ReaderHandler) GetPDFThumbnail(c *echo.Context) error {
	mediaItemID := c.Param("mediaItemId")
	pageNumber := c.Param("pageNumber")

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

	page, err := strconv.Atoi(pageNumber)
	if err != nil || page < 1 {
		return c.JSON(http.StatusBadRequest, map[string]string{"error": "Invalid page number"})
	}

	// Get media item
	mediaItem, err := h.db.GetMediaItem(c.Request().Context(), pgtype.UUID{Bytes: parsedUUID, Valid: true})
	if err != nil {
		return c.JSON(http.StatusNotFound, map[string]string{"error": "Media item not found"})
	}

	// Get thumbnail size from query
	width := c.QueryParam("width")
	height := c.QueryParam("height")

	// Generate thumbnail using pdfcpu
	fullPath, err := h.getFullFilePath(c.Request().Context(), mediaItem.LibraryID, mediaItem.FilePath)
	if err != nil {
		return c.JSON(http.StatusNotFound, map[string]string{"error": "File not found"})
	}

	thumbnail, err := h.generatePDFThumbnail(fullPath, page, width, height)
	if err != nil {
		return c.JSON(http.StatusInternalServerError, map[string]string{"error": "Failed to generate thumbnail"})
	}

	c.Response().Header().Set("Content-Type", "image/png")
	return c.Blob(http.StatusOK, "image/png", thumbnail)
}

// LookupWord performs dictionary lookup
func (h *ReaderHandler) LookupWord(c *echo.Context) error {
	word := c.Param("word")
	if word == "" {
		return c.JSON(http.StatusBadRequest, map[string]string{"error": "Word parameter required"})
	}

	// Use reader service for dictionary lookup
	entry, err := h.readerService.LookupWord(c.Request().Context(), word)
	if err != nil {
		return c.JSON(http.StatusNotFound, map[string]string{"error": "Word not found in dictionary"})
	}

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

// GetSettings retrieves user's reader settings
func (h *ReaderHandler) GetSettings(c *echo.Context) error {
	user := c.Get("user").(database.Users)

	// Use reader service to get settings
	settings, err := h.readerService.GetSettings(c.Request().Context(), user.ID.Bytes)
	if err != nil {
		return c.JSON(http.StatusInternalServerError, map[string]string{"error": "Failed to fetch settings"})
	}

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

// UpdateSettings updates user's reader settings
func (h *ReaderHandler) UpdateSettings(c *echo.Context) error {
	user := c.Get("user").(database.Users)

	// Parse request body (partial update supported)
	var settings map[string]interface{}
	if err := c.Bind(&settings); err != nil {
		return c.JSON(http.StatusBadRequest, map[string]string{"error": "Invalid request body"})
	}

	// Validate settings
	if readingTheme, ok := settings["reading_theme"].(string); ok {
		validThemes := map[string]bool{
			"light": true, "paper": true, "sepia": true, "parchment": true,
			"warm": true, "candlelight": true, "azure": true, "sky": true,
			"arctic": true, "frost": true, "dusk": true, "sunset": true,
			"twilight": true, "forest": true, "moss": true, "slate": true,
			"oled": true, "solarized": true,
		}
		if !validThemes[readingTheme] {
			return c.JSON(http.StatusBadRequest, map[string]string{"error": "Invalid reading theme"})
		}
	}

	// Use reader service to update settings
	err := h.readerService.UpdateSettings(c.Request().Context(), user.ID.Bytes, settings)
	if err != nil {
		return c.JSON(http.StatusInternalServerError, map[string]string{"error": "Failed to update settings"})
	}

	// Return updated settings
	updatedSettings, _ := h.readerService.GetSettings(c.Request().Context(), user.ID.Bytes)
	return c.JSON(http.StatusOK, updatedSettings)
}

// Helper functions

func (h *ReaderHandler) getFullFilePath(ctx context.Context, libraryID pgtype.UUID, relativePath string) (string, error) {
	// Get library folders
	folders, err := h.db.GetLibraryFolders(ctx, libraryID)
	if err != nil {
		return "", err
	}

	// Try each folder until we find the file
	for _, folder := range folders {
		fullPath := folder.FolderPath + string(os.PathSeparator) + relativePath
		if _, err := os.Stat(fullPath); err == nil {
			return fullPath, nil
		}
	}

	return "", fmt.Errorf("file not found in any library folder")
}

func (h *ReaderHandler) extractPageContent(ctx context.Context, item *database.MediaItems, page int, format, fullPath string) (string, error) {
	// Extract content based on format
	// This is a simplified implementation
	// In production, would use format-specific parsers

	switch item.FormatGroup {
	case "reflowable":
		// For EPUB and other ebooks, extract the requested page/chapter
		return h.extractEbookPage(fullPath, page, format)
	case "fixed-layout":
		// For comics, return image data URL or path
		return h.extractComicPage(fullPath, page, format)
	case "pdf":
		// For PDFs, extract text or image
		return h.extractPDFPage(fullPath, page, format)
	default:
		return "", fmt.Errorf("unsupported format: %s", item.FormatGroup)
	}
}

func (h *ReaderHandler) extractEbookPage(fullPath string, page int, format string) (string, error) {
	// Simplified EPUB extraction
	// In production, would use epub-parser.ts logic
	return fmt.Sprintf("<div class='ebook-page'><p>Page %d content</p></div>", page), nil
}

func (h *ReaderHandler) extractComicPage(fullPath string, page int, format string) (string, error) {
	// For comics, return image path or data URL
	return fmt.Sprintf("/api/readers/comic-image?page=%d", page), nil
}

func (h *ReaderHandler) extractPDFPage(fullPath string, page int, format string) (string, error) {
	// For PDFs, extract text content or image
	return fmt.Sprintf("<div class='pdf-page'><p>Page %d content</p></div>", page), nil
}

func (h *ReaderHandler) extractPDFOutline(fullPath string) []map[string]interface{} {
	// Extract PDF outline using pdfcpu
	// This is a placeholder
	return []map[string]interface{}{}
}

func (h *ReaderHandler) generatePDFThumbnail(fullPath string, page int, width, height string) ([]byte, error) {
	// Generate thumbnail using pdfcpu
	// This is a placeholder
	return []byte{}, nil
}

func (h *ReaderHandler) ParseEbook(c *echo.Context) error {
	mediaItemID := c.Param("mediaItemId")
	parsedUUID, err := uuid.Parse(mediaItemID)
	if err != nil {
		return c.JSON(400, map[string]string{"error": "Invalid media item ID"})
	}

	// Fetch media item
	mediaItem, err := h.db.GetMediaItem(c.Request().Context(), pgtype.UUID{Bytes: parsedUUID, Valid: true})
	if err != nil {
		if errors.Is(err, pgx.ErrNoRows) {
			return c.JSON(404, map[string]string{"error": "Media item not found"})
		}
		return c.JSON(500, map[string]string{"error": "Failed to fetch media item"})
	}

	// Check if format requires server-side parsing
	requiresServerParsing := false
	formatName := ""
	switch mediaItem.MimeType.String {
	case "application/x-mobipocket-ebook":
		requiresServerParsing = true
		formatName = "MOBI"
	case "application/vnd.amazon.mobi8-ebook":
		requiresServerParsing = true
		formatName = "AZW3/KF8"
	case "application/vnd.openxmlformats-officedocument.wordprocessingml.document":
		requiresServerParsing = true
		formatName = "DOCX"
	case "application/rtf":
		requiresServerParsing = true
		formatName = "RTF"
	}

	if requiresServerParsing {
		// Phase 1: Return 501 for server-side formats
		return c.JSON(501, map[string]interface{}{
			"error":      fmt.Sprintf("Server-side %s parsing is not yet implemented", formatName),
			"message":    fmt.Sprintf("%s format support is planned for Phase 2.5", formatName),
			"suggestion": "Please convert your ebook to EPUB format for now",
			"format":     formatName,
			"phase":      "2.5",
		})
	}

	// Client-side format - should not call this endpoint
	return c.JSON(400, map[string]string{
		"error": "This format should be parsed client-side, not on the server",
	})
}

// shouldEnablePanelDetection determines if panel detection should be enabled
// based on library type and format group
func shouldEnablePanelDetection(libraryType string, formatGroup string) bool {
	panelLibraries := map[string]bool{
		"manga":  true,
		"comics": true,
	}

	panelFormats := map[string]bool{
		"fixed_layout":  true,
		"comic_archive": true,
	}

	return panelLibraries[libraryType] && panelFormats[formatGroup]
}
