package service

import (
	"context"
	"encoding/json"
	"fmt"
	"log/slog"
	"strconv"
	"strings"
	"sync"
	"time"

	"github.com/TopherMayor/unified-media-manager/internal/db"
)

// DiscoverItem represents a single item returned from the discover endpoints.
type DiscoverItem struct {
	TMDBID      int     `json:"tmdb_id"`
	Title       string  `json:"title"`
	Year        *int    `json:"year,omitempty"`
	MediaType   string  `json:"media_type"`
	Overview    string  `json:"overview,omitempty"`
	PosterURL   string  `json:"poster_url,omitempty"`
	BackdropURL string  `json:"backdrop_url,omitempty"`
	VoteAverage float64 `json:"vote_average"`
	InLibrary   bool    `json:"in_library"`
}

type discoverCacheEntry struct {
	data      []DiscoverItem
	expiresAt time.Time
}

// DiscoverService provides trending/popular browsing and add-to-library functionality.
type DiscoverService struct {
	tmdb  *TMDBProvider
	db    *db.DB
	cache sync.Map
}

// NewDiscoverService creates a new DiscoverService.
func NewDiscoverService(tmdb *TMDBProvider, database *db.DB) *DiscoverService {
	return &DiscoverService{tmdb: tmdb, db: database}
}

const discoverCacheTTL = 6 * time.Hour

// Trending returns trending items from TMDB, checking an in-memory cache first.
func (s *DiscoverService) Trending(ctx context.Context, mediaType string, page int) ([]DiscoverItem, error) {
	cacheKey := fmt.Sprintf("trending:%s:%d", mediaType, page)
	if cached, ok := s.cache.Load(cacheKey); ok {
		entry := cached.(*discoverCacheEntry)
		if time.Now().Before(entry.expiresAt) {
			return entry.data, nil
		}
		s.cache.Delete(cacheKey)
	}

	items, err := s.tmdb.Trending(ctx, mediaType, page)
	if err != nil {
		return nil, fmt.Errorf("fetch trending: %w", err)
	}

	result := s.convertItems(ctx, items, mediaType)

	s.cache.Store(cacheKey, &discoverCacheEntry{
		data:      result,
		expiresAt: time.Now().Add(discoverCacheTTL),
	})

	return result, nil
}

// Popular returns popular items from TMDB, checking an in-memory cache first.
func (s *DiscoverService) Popular(ctx context.Context, mediaType string, page int) ([]DiscoverItem, error) {
	cacheKey := fmt.Sprintf("popular:%s:%d", mediaType, page)
	if cached, ok := s.cache.Load(cacheKey); ok {
		entry := cached.(*discoverCacheEntry)
		if time.Now().Before(entry.expiresAt) {
			return entry.data, nil
		}
		s.cache.Delete(cacheKey)
	}

	items, err := s.tmdb.Popular(ctx, mediaType, page)
	if err != nil {
		return nil, fmt.Errorf("fetch popular: %w", err)
	}

	result := s.convertItems(ctx, items, mediaType)

	s.cache.Store(cacheKey, &discoverCacheEntry{
		data:      result,
		expiresAt: time.Now().Add(discoverCacheTTL),
	})

	return result, nil
}

// AddToLibrary adds a TMDB item to the user's monitored library.
// If the item already exists, it returns the existing ID with no error.
func (s *DiscoverService) AddToLibrary(ctx context.Context, tmdbID int, mediaType string) (int64, bool, error) {
	// Check if already in library
	var existingID int64
	err := s.db.Pool.QueryRow(ctx,
		`SELECT id FROM media WHERE external_ids @> $1::jsonb AND deleted_at IS NULL LIMIT 1`,
		fmt.Sprintf(`{"tmdb":"%d"}`, tmdbID)).Scan(&existingID)
	if err == nil {
		return existingID, true, nil
	}

	// Fetch full details from TMDB
	detail, err := s.fetchFullDetail(ctx, tmdbID, mediaType)
	if err != nil {
		return 0, false, fmt.Errorf("fetch tmdb detail: %w", err)
	}

	req := s.buildCreateRequest(detail, mediaType)

	newID, err := NewMediaService(s.db).Create(ctx, req)
	if err != nil {
		return 0, false, fmt.Errorf("create media: %w", err)
	}

	return newID, false, nil
}

func (s *DiscoverService) convertItems(ctx context.Context, items []tmdbSearchItem, mediaType string) []DiscoverItem {
	if len(items) == 0 {
		return []DiscoverItem{}
	}

	// Collect TMDB IDs for batch library check
	tmdbIDs := make([]int, len(items))
	for i, item := range items {
		tmdbIDs[i] = item.ID
	}

	libMembership := s.checkLibraryMembership(ctx, tmdbIDs, mediaType)

	result := make([]DiscoverItem, 0, len(items))
	for _, item := range items {
		title := item.Title
		dateStr := item.ReleaseDate
		mType := "movie"

		if mediaType == "series" || item.MediaType == "tv" {
			title = item.Name
			if title == "" {
				title = item.Title
			}
			dateStr = item.FirstAirDate
			mType = "series"
		}
		if title == "" {
			title = item.Name
		}

		year := parseTMDBYear(dateStr)

		result = append(result, DiscoverItem{
			TMDBID:      item.ID,
			Title:       title,
			Year:        year,
			MediaType:   mType,
			Overview:    item.Overview,
			PosterURL:   buildPosterURL(item.PosterPath),
			BackdropURL: buildBackdropURL(item.BackdropPath),
			VoteAverage: item.VoteAverage,
			InLibrary:   libMembership[item.ID],
		})
	}
	return result
}

func (s *DiscoverService) checkLibraryMembership(ctx context.Context, tmdbIDs []int, _ string) map[int]bool {
	membership := make(map[int]bool)
	if len(tmdbIDs) == 0 {
		return membership
	}

	// Build JSONB array condition for batch check
	conditions := make([]string, len(tmdbIDs))
	args := make([]interface{}, len(tmdbIDs))
	for i, id := range tmdbIDs {
		conditions[i] = fmt.Sprintf("external_ids @> $%d::jsonb", i+1)
		args[i] = fmt.Sprintf(`{"tmdb":"%d"}`, id)
	}
	query := fmt.Sprintf(
		"SELECT external_ids FROM media WHERE (%s) AND deleted_at IS NULL",
		strings.Join(conditions, " OR "),
	)

	rows, err := s.db.Pool.Query(ctx, query, args...)
	if err != nil {
		slog.Error("check library membership", "error", err)
		return membership
	}
	defer rows.Close()

	for rows.Next() {
		var extIDs json.RawMessage
		if err := rows.Scan(&extIDs); err != nil {
			continue
		}
		var ids map[string]string
		if json.Unmarshal(extIDs, &ids) == nil {
			if idStr, ok := ids["tmdb"]; ok {
				if id, err := strconv.Atoi(idStr); err == nil {
					membership[id] = true
				}
			}
		}
	}
	return membership
}

func (s *DiscoverService) fetchFullDetail(ctx context.Context, tmdbID int, mediaType string) (*TMDBFullDetail, error) {
	idStr := strconv.Itoa(tmdbID)
	if mediaType == "series" {
		return s.tmdb.GetTVDetails(ctx, idStr)
	}
	return s.tmdb.GetMovieDetails(ctx, idStr)
}

func (s *DiscoverService) buildCreateRequest(detail *TMDBFullDetail, mediaType string) CreateMediaRequest {
	title := detail.Title
	dateStr := detail.ReleaseDate
	if mediaType == "series" {
		if detail.Name != "" {
			title = detail.Name
		}
		dateStr = detail.FirstAirDate
	}

	year := parseTMDBYear(dateStr)
	overview := detail.Overview

	// Parse release_date for the dedicated column
	var releaseDate *time.Time
	if dateStr != "" {
		if parsed, err := time.Parse("2006-01-02", dateStr); err == nil {
			releaseDate = &parsed
		}
	}

	// Build external IDs
	extIDs := map[string]string{
		"tmdb": strconv.Itoa(detail.ID),
	}
	if detail.ExternalIDs.IMDbID != "" {
		extIDs["imdb"] = detail.ExternalIDs.IMDbID
	}
	if detail.ExternalIDs.TVDBID != "" {
		extIDs["tvdb"] = detail.ExternalIDs.TVDBID
	}
	extIDsJSON, _ := json.Marshal(extIDs)

	// Build metadata
	meta := map[string]interface{}{
		"tmdb_rating": detail.VoteAverage,
	}
	var genreNames []string
	for _, g := range detail.Genres {
		genreNames = append(genreNames, g.Name)
	}
	if len(genreNames) > 0 {
		meta["genres"] = genreNames
	}
	if detail.Runtime > 0 {
		meta["runtime"] = detail.Runtime
	}
	if mediaType == "series" {
		meta["number_of_seasons"] = detail.NumberOfSeasons
		meta["number_of_episodes"] = detail.NumberOfEpisodes
	}
	// Store date string in metadata for reference
	if mediaType == "movie" && detail.ReleaseDate != "" {
		meta["release_date"] = detail.ReleaseDate
	}
	if mediaType == "series" && detail.FirstAirDate != "" {
		meta["first_air_date"] = detail.FirstAirDate
	}
	metaJSON, _ := json.Marshal(meta)

	// Build images
	var images []map[string]interface{}
	if detail.PosterPath != "" {
		images = append(images, map[string]interface{}{
			"url":  fmt.Sprintf("https://image.tmdb.org/t/p/original%s", detail.PosterPath),
			"type": "poster",
		})
	}
	if detail.BackdropPath != "" {
		images = append(images, map[string]interface{}{
			"url":  fmt.Sprintf("https://image.tmdb.org/t/p/original%s", detail.BackdropPath),
			"type": "backdrop",
		})
	}
	imagesJSON, _ := json.Marshal(images)

	return CreateMediaRequest{
		MediaType:   mediaType,
		Title:       title,
		Overview:    &overview,
		Year:        year,
		ReleaseDate: releaseDate,
		Status:      "unavailable",
		Monitored:   true,
		ExternalIDs: extIDsJSON,
		Metadata:    metaJSON,
		Images:      imagesJSON,
	}
}

func buildPosterURL(path string) string {
	if path == "" {
		return ""
	}
	return "https://image.tmdb.org/t/p/w500" + path
}

func buildBackdropURL(path string) string {
	if path == "" {
		return ""
	}
	return "https://image.tmdb.org/t/p/w780" + path
}
