package services

import (
	"bookhoard/internal/database"
	"context"
	"encoding/json"
	"fmt"
	"time"

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

type ReaderService struct {
	db     *database.Queries
	worker *Worker
}

func NewReaderService(db *database.Queries, worker *Worker) *ReaderService {
	return &ReaderService{
		db:     db,
		worker: worker,
	}
}

// Chapter represents a detected chapter
type Chapter struct {
	ID        string  `json:"id"`
	Title     string  `json:"title"`
	StartPage int     `json:"start_page"`
	PageCount int     `json:"page_count"`
	Level     int     `json:"level"`
	ParentID  *string `json:"parent_id,omitempty"`
}

// Panel represents a detected comic panel
type Panel struct {
	ID           string `json:"id"`
	X            int    `json:"x"`
	Y            int    `json:"y"`
	Width        int    `json:"width"`
	Height       int    `json:"height"`
	ReadingOrder int    `json:"reading_order"`
}

// DictionaryEntry represents a word definition
type DictionaryEntry struct {
	Word         string `json:"word"`
	Definition   string `json:"definition"`
	PartOfSpeech string `json:"part_of_speech,omitempty"`
	Example      string `json:"example,omitempty"`
	Etymology    string `json:"etymology,omitempty"`
}

// ChapterDetectionResult contains chapter metadata
type ChapterDetectionResult struct {
	Chapters   []Chapter       `json:"chapters"`
	Metadata   json.RawMessage `json:"metadata,omitempty"`
	DetectedAt time.Time       `json:"detected_at"`
}

// DetectChapters analyzes a media item to detect chapter structure
func (s *ReaderService) DetectChapters(ctx context.Context, mediaItemID uuid.UUID) ([]Chapter, error) {
	// Get media item to determine type
	item, err := s.db.GetMediaItem(ctx, pgtype.UUID{Bytes: mediaItemID, Valid: true})
	if err != nil {
		return nil, fmt.Errorf("failed to get media item: %w", err)
	}

	// Check if chapter metadata already exists
	if len(item.ChapterMetadata) > 0 {
		var existing ChapterDetectionResult
		if err := json.Unmarshal(item.ChapterMetadata, &existing); err == nil {
			return existing.Chapters, nil
		}
	}

	// Detect chapters based on format
	var chapters []Chapter

	switch item.FormatGroup {
	case "reflowable":
		// For ebooks, parse from TOC if available
		chapters, err = s.detectEbookChapters(ctx, &item)
	case "fixed-layout":
		// For comics/manga, detect page breaks as chapters
		chapters, err = s.detectComicChapters(ctx, &item)
	case "pdf":
		// For PDFs, use PDF outline
		chapters, err = s.detectPDFChapters(ctx, &item)
	default:
		chapters = []Chapter{}
	}

	if err != nil {
		return nil, fmt.Errorf("chapter detection failed: %w", err)
	}

	// Cache the results
	result := ChapterDetectionResult{
		Chapters:   chapters,
		Metadata:   nil,
		DetectedAt: time.Now(),
	}

	metadataBytes, err := json.Marshal(result)
	if err == nil {
		_, _ = s.db.UpdateMediaItemChapterMetadata(ctx, database.UpdateMediaItemChapterMetadataParams{
			ID:              pgtype.UUID{Bytes: mediaItemID, Valid: true},
			ChapterMetadata: metadataBytes,
		})
	}

	return chapters, nil
}

func (s *ReaderService) detectEbookChapters(ctx context.Context, item *database.MediaItems) ([]Chapter, error) {
	// For EPUB files, parse the TOC from the OPF file
	// This requires EPUB parsing (see epub-parser.ts)
	// For now, return empty structure
	return []Chapter{}, nil
}

func (s *ReaderService) detectComicChapters(ctx context.Context, item *database.MediaItems) ([]Chapter, error) {
	// For comics, treat each page as a potential chapter
	// or group pages by story arcs if metadata exists
	pageCount := int(item.PageCount.Int32)

	if pageCount <= 0 {
		return []Chapter{}, nil
	}

	chapters := make([]Chapter, 0)
	chapterSize := 20 // Group pages into chapters of 20 pages each

	for i := 0; i < pageCount; i += chapterSize {
		endPage := i + chapterSize
		if endPage > pageCount {
			endPage = pageCount
		}

		chapters = append(chapters, Chapter{
			ID:        fmt.Sprintf("chapter-%d", len(chapters)+1),
			Title:     fmt.Sprintf("Page %d-%d", i+1, endPage),
			StartPage: i + 1,
			PageCount: endPage - i,
			Level:     1,
		})
	}

	return chapters, nil
}

func (s *ReaderService) detectPDFChapters(ctx context.Context, item *database.MediaItems) ([]Chapter, error) {
	// For PDFs, use pdfcpu to extract outline/bookmarks
	// This requires PDF parsing library
	return []Chapter{}, nil
}

// DetectPanels analyzes a comic page to detect panel boundaries
func (s *ReaderService) DetectPanels(
	ctx context.Context,
	mediaItemID uuid.UUID,
	pageNumber int,
	method string,
) ([]Panel, error) {
	// Check if panels already exist in cache
	cached, err := s.db.GetPanelData(ctx, database.GetPanelDataParams{
		MediaItemID: pgtype.UUID{Bytes: mediaItemID, Valid: true},
		PageNumber:  int32(pageNumber),
	})

	if err == nil {
		var panels []Panel
		if err := json.Unmarshal(cached.Panels, &panels); err == nil {
			return panels, nil
		}
	}

	// Detect panels using specified method
	var panels []Panel

	switch method {
	case "grid":
		panels, err = s.detectPanelsGrid(ctx, mediaItemID, pageNumber)
	case "ml":
		panels, err = s.detectPanelsML(ctx, mediaItemID, pageNumber)
	case "manual":
		panels, err = s.detectPanelsManual(ctx, mediaItemID, pageNumber)
	default:
		// Default to grid detection
		panels, err = s.detectPanelsGrid(ctx, mediaItemID, pageNumber)
	}

	if err != nil {
		return nil, fmt.Errorf("panel detection failed: %w", err)
	}

	// Cache the results
	panelsJSON, _ := json.Marshal(panels)
	// Insert into panel_data table
	_ = panelsJSON

	return panels, nil
}

func (s *ReaderService) detectPanelsGrid(
	ctx context.Context,
	mediaItemID uuid.UUID,
	pageNumber int,
) ([]Panel, error) {
	// Simple grid-based panel detection
	// Divide page into 2x2 or 3x3 grid
	// This is a simplified implementation

	panels := []Panel{
		{
			ID:           "panel-1",
			X:            0,
			Y:            0,
			Width:        50,
			Height:       100,
			ReadingOrder: 1,
		},
		{
			ID:           "panel-2",
			X:            50,
			Y:            0,
			Width:        50,
			Height:       100,
			ReadingOrder: 2,
		},
	}

	return panels, nil
}

func (s *ReaderService) detectPanelsML(
	ctx context.Context,
	mediaItemID uuid.UUID,
	pageNumber int,
) ([]Panel, error) {
	// ML-based panel detection
	// This would require a trained model
	// For now, fall back to grid detection
	return s.detectPanelsGrid(ctx, mediaItemID, pageNumber)
}

func (s *ReaderService) detectPanelsManual(
	ctx context.Context,
	mediaItemID uuid.UUID,
	pageNumber int,
) ([]Panel, error) {
	// Manual panel detection returns existing manually-set panels
	// These would be stored in the panel_data table
	return []Panel{}, nil
}

// CalculateReadingSpeed updates reading speed statistics
func (s *ReaderService) CalculateReadingSpeed(
	ctx context.Context,
	userID uuid.UUID,
	mediaItemID uuid.UUID,
	pagesRead int,
	minutes float32,
) error {
	if minutes <= 0 {
		return fmt.Errorf("invalid time: must be positive")
	}

	pagesPerMinute := float32(pagesRead) / minutes

	// Get or create reading speed record
	_, err := s.db.GetReadingSpeed(ctx, database.GetReadingSpeedParams{
		UserID:      pgtype.UUID{Bytes: userID, Valid: true},
		MediaItemID: pgtype.UUID{Bytes: mediaItemID, Valid: true},
	})

	if err != nil {
		// Create new record
		_, err = s.db.CreateReadingSpeed(ctx, database.CreateReadingSpeedParams{
			UserID:              pgtype.UUID{Bytes: userID, Valid: true},
			MediaItemID:         pgtype.UUID{Bytes: mediaItemID, Valid: true},
			PagesPerMinute:      pgtype.Float4{Float32: pagesPerMinute, Valid: true},
			PagesRead:           pgtype.Int4{Int32: int32(pagesRead), Valid: true},
			TotalReadingMinutes: pgtype.Float4{Float32: minutes, Valid: true},
			LastReadAt:          pgtype.Timestamptz{Time: time.Now(), Valid: true},
		})
	} else {
		// Update existing record with moving average
		// This would require an UpdateReadingSpeed query
		_ = pagesPerMinute
	}

	return err
}

// LookupWord retrieves dictionary entry for a word
func (s *ReaderService) LookupWord(ctx context.Context, word string) (*DictionaryEntry, error) {
	// Check cache first
	cached, err := s.db.GetDictionaryEntry(ctx, word)

	if err == nil {
		return &DictionaryEntry{
			Word:         cached.Word,
			Definition:   cached.Definition,
			PartOfSpeech: cached.PartOfSpeech.String,
			Example:      cached.Example.String,
			Etymology:    cached.Etymology.String,
		}, nil
	}

	// Not in cache, fetch from dictionary API
	entry, err := s.fetchDictionaryEntry(ctx, word)
	if err != nil {
		return nil, err
	}

	// Cache the entry
	_, _ = s.db.CreateDictionaryEntry(ctx, database.CreateDictionaryEntryParams{
		Word:         entry.Word,
		Definition:   entry.Definition,
		PartOfSpeech: pgtype.Text{String: entry.PartOfSpeech, Valid: entry.PartOfSpeech != ""},
		Example:      pgtype.Text{String: entry.Example, Valid: entry.Example != ""},
		Etymology:    pgtype.Text{String: entry.Etymology, Valid: entry.Etymology != ""},
	})

	return entry, nil
}

func (s *ReaderService) fetchDictionaryEntry(ctx context.Context, word string) (*DictionaryEntry, error) {
	// Fetch from external dictionary API
	// For now, return a placeholder
	return &DictionaryEntry{
		Word:       word,
		Definition: fmt.Sprintf("Definition for %s", word),
	}, nil
}

// GetSettings retrieves reader settings for a user
func (s *ReaderService) GetSettings(
	ctx context.Context,
	userID uuid.UUID,
) (map[string]interface{}, error) {
	// Get settings from database
	settings, err := s.db.GetReaderSettings(ctx, pgtype.UUID{Bytes: userID, Valid: true})

	if err != nil {
		// Return default settings
		return s.getDefaultSettings(), nil
	}

	var result map[string]interface{}
	if err := json.Unmarshal(settings, &result); err != nil {
		return s.getDefaultSettings(), nil
	}

	return result, nil
}

// UpdateSettings updates reader settings for a user
func (s *ReaderService) UpdateSettings(
	ctx context.Context,
	userID uuid.UUID,
	settings map[string]interface{},
) error {
	// Merge with existing settings
	existing, err := s.GetSettings(ctx, userID)
	if err != nil {
		existing = s.getDefaultSettings()
	}

	// Merge settings (partial update)
	for key, value := range settings {
		existing[key] = value
	}

	// Serialize and save
	settingsJSON, err := json.Marshal(existing)
	if err != nil {
		return fmt.Errorf("failed to serialize settings: %w", err)
	}

	// Update in database
	_, err = s.db.UpsertReaderSettings(ctx, database.UpsertReaderSettingsParams{
		UserID:       pgtype.UUID{Bytes: userID, Valid: true},
		SettingValue: settingsJSON,
	})

	return err
}

func (s *ReaderService) getDefaultSettings() map[string]interface{} {
	return map[string]interface{}{
		"chrome_behavior":      "auto-hide",
		"progress_mode":        "pages",
		"chrome_theme":         "tokyo-night",
		"reading_theme":        "dark",
		"reading_font":         "literata",
		"font_size":            16,
		"line_height":          1.6,
		"margin_width":         20,
		"tap_zone_size":        30,
		"auto_scroll":          false,
		"panel_zoom_enabled":   true,
		"double_page_spread":   true,
		"pdf_interaction_mode": "select",
		"tap_zones_enabled":    true,

		// Dockable panel defaults
		"panel_layout": map[string]interface{}{
			"toc":                map[string]interface{}{"side": "left", "visible": true, "collapsed": false, "width_px": 320, "order": 1, "locked": false, "last_valid_side": "left"},
			"settings":           map[string]interface{}{"side": "left", "visible": false, "collapsed": true, "width_px": 380, "order": 2, "locked": false, "last_valid_side": "left"},
			"navigator":          map[string]interface{}{"side": "right", "visible": true, "collapsed": false, "width_px": 200, "order": 1, "locked": false, "last_valid_side": "right"},
			"bookmarks":          map[string]interface{}{"side": "right", "visible": false, "collapsed": true, "width_px": 280, "order": 2, "locked": false, "last_valid_side": "right"},
			"mobile_nav_visible": false,
		},
	}
}
