package service

import (
	"context"
	"encoding/json"
	"fmt"
	"log/slog"
	"net/http"
	"strconv"
	"time"
)

type TMDBProvider struct {
	apiKey     string
	baseURL    string
	httpClient *http.Client
}

func NewTMDBProvider(apiKey string) *TMDBProvider {
	return &TMDBProvider{
		apiKey:  apiKey,
		baseURL: "https://api.themoviedb.org/3",
		httpClient: &http.Client{
			Timeout: 10 * time.Second,
		},
	}
}

type tmdbSearchResponse struct {
	Page        int            `json:"page"`
	TotalResults int           `json:"total_results"`
	Results     []tmdbSearchItem `json:"results"`
}

type tmdbSearchItem struct {
	ID             int     `json:"id"`
	Title          string  `json:"title"`
	Name           string  `json:"name"`
	OriginalTitle  string  `json:"original_title"`
	OriginalName   string  `json:"original_name"`
	Overview       string  `json:"overview"`
	ReleaseDate    string  `json:"release_date"`
	FirstAirDate   string  `json:"first_air_date"`
	PosterPath     string  `json:"poster_path"`
	BackdropPath   string  `json:"backdrop_path"`
	VoteAverage    float64 `json:"vote_average"`
	MediaType      string  `json:"media_type"`
}

type tmdbMovieDetail struct {
	ID            int          `json:"id"`
	Title         string       `json:"title"`
	OriginalTitle string       `json:"original_title"`
	Overview      string       `json:"overview"`
	ReleaseDate   string       `json:"release_date"`
	PosterPath    string       `json:"poster_path"`
	BackdropPath  string       `json:"backdrop_path"`
	VoteAverage   float64      `json:"vote_average"`
	Runtime       int          `json:"runtime"`
	Genres        []tmdbGenre  `json:"genres"`
	ExternalIDs   tmdbExternalIDs `json:"external_ids"`
}

type tmdbTVDetail struct {
	ID              int          `json:"id"`
	Name            string       `json:"name"`
	OriginalName    string       `json:"original_name"`
	Overview        string       `json:"overview"`
	FirstAirDate    string       `json:"first_air_date"`
	PosterPath      string       `json:"poster_path"`
	BackdropPath    string       `json:"backdrop_path"`
	VoteAverage     float64      `json:"vote_average"`
	NumberOfSeasons int          `json:"number_of_seasons"`
	NumberOfEpisodes int         `json:"number_of_episodes"`
	Genres          []tmdbGenre  `json:"genres"`
	ExternalIDs     tmdbExternalIDs `json:"external_ids"`
}

type tmdbGenre struct {
	ID   int    `json:"id"`
	Name string `json:"name"`
}

type tmdbExternalIDs struct {
	IMDbID string `json:"imdb_id"`
	TVDBID string `json:"tvdb_id"`
}

type TMDBFullDetail struct {
	ID               int             `json:"id"`
	Title            string          `json:"title"`
	Name             string          `json:"name"`
	Overview         string          `json:"overview"`
	PosterPath       string          `json:"poster_path"`
	BackdropPath     string          `json:"backdrop_path"`
	ReleaseDate      string          `json:"release_date"`
	FirstAirDate     string          `json:"first_air_date"`
	VoteAverage      float64         `json:"vote_average"`
	Genres           []tmdbGenre     `json:"genres"`
	ExternalIDs      tmdbExtIDs      `json:"external_ids"`
	NumberOfSeasons  int             `json:"number_of_seasons"`
	NumberOfEpisodes int             `json:"number_of_episodes"`
	Runtime          int             `json:"runtime"`
	Status           string          `json:"status"`
}

type tmdbExtIDs struct {
	IMDbID string `json:"imdb_id"`
	TVDBID string `json:"tvdb_id"`
}

type tmdbImage struct {
	FilePath     string  `json:"file_path"`
	Width        int     `json:"width"`
	Height       int     `json:"height"`
	AspectRatio  float64 `json:"aspect_ratio"`
	VoteAverage  float64 `json:"vote_average"`
}

type tmdbImagesResponse struct {
	Backdrops []tmdbImage `json:"backdrops"`
	Posters   []tmdbImage `json:"posters"`
}

func (p *TMDBProvider) Name() string {
	return "tmdb"
}

func (p *TMDBProvider) fetchTMDB(ctx context.Context, url string, result interface{}) error {
	req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
	if err != nil {
		return fmt.Errorf("create request: %w", err)
	}

	resp, err := p.httpClient.Do(req)
	if err != nil {
		return fmt.Errorf("fetch tmdb: %w", err)
	}
	defer resp.Body.Close()

	if resp.StatusCode != http.StatusOK {
		return fmt.Errorf("tmdb api returned status %d", resp.StatusCode)
	}

	if err := json.NewDecoder(resp.Body).Decode(result); err != nil {
		return fmt.Errorf("decode tmdb response: %w", err)
	}

	return nil
}

func parseTMDBYear(dateStr string) *int {
	if dateStr == "" {
		return nil
	}
	if len(dateStr) < 4 {
		return nil
	}
	year, err := strconv.Atoi(dateStr[:4])
	if err != nil {
		return nil
	}
	return &year
}

func (p *TMDBProvider) Search(ctx context.Context, query string, opts SearchOptions) ([]MetadataSearchResult, error) {
	url := fmt.Sprintf("%s/search/multi?api_key=%s&query=%s", p.baseURL, p.apiKey, query)
	if opts.Page > 0 {
		url += fmt.Sprintf("&page=%d", opts.Page)
	}
	if opts.Year != nil {
		url += fmt.Sprintf("&year=%d", *opts.Year)
	}

	var resp tmdbSearchResponse
	if err := p.fetchTMDB(ctx, url, &resp); err != nil {
		return nil, fmt.Errorf("search tmdb: %w", err)
	}

	var results []MetadataSearchResult
	for _, item := range resp.Results {
		if item.MediaType != "movie" && item.MediaType != "tv" {
			continue
		}

		title := item.Title
		origTitle := item.OriginalTitle
		dateStr := item.ReleaseDate
		mediaType := "movie"

		if item.MediaType == "tv" {
			title = item.Name
			origTitle = item.OriginalName
			dateStr = item.FirstAirDate
			mediaType = "series"
		}

		year := parseTMDBYear(dateStr)

		externalIDs, _ := json.Marshal(map[string]string{
			"tmdb": strconv.Itoa(item.ID),
		})

		overview := item.Overview

		results = append(results, MetadataSearchResult{
			ProviderID:    strconv.Itoa(item.ID),
			Title:         title,
			Year:          year,
			MediaType:     mediaType,
			Overview:      overview,
			OriginalTitle: origTitle,
			ExternalIDs:   externalIDs,
		})
	}

	return results, nil
}

func (p *TMDBProvider) GetDetails(ctx context.Context, id string) (*MetadataDetails, error) {
	movieURL := fmt.Sprintf("%s/movie/%s?api_key=%s&append_to_response=external_ids", p.baseURL, id, p.apiKey)

	var movieDetail tmdbMovieDetail
	err := p.fetchTMDB(ctx, movieURL, &movieDetail)
	if err == nil {
		ratings, _ := json.Marshal(map[string]float64{
			"tmdb": movieDetail.VoteAverage,
		})

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

		var genreNames []string
		for _, g := range movieDetail.Genres {
			genreNames = append(genreNames, g.Name)
		}

		metadata, _ := json.Marshal(map[string]interface{}{
			"runtime":      movieDetail.Runtime,
			"genres":       genreNames,
			"release_date": movieDetail.ReleaseDate,
		})

		overview := movieDetail.Overview
		origTitle := movieDetail.OriginalTitle

		return &MetadataDetails{
			ProviderID:    id,
			Title:         movieDetail.Title,
			OriginalTitle: &origTitle,
			Overview:      &overview,
			Year:          parseTMDBYear(movieDetail.ReleaseDate),
			Ratings:       ratings,
			ExternalIDs:   extIDsJSON,
			Metadata:      metadata,
		}, nil
	}

	tvURL := fmt.Sprintf("%s/tv/%s?api_key=%s&append_to_response=external_ids", p.baseURL, id, p.apiKey)

	var tvDetail tmdbTVDetail
	if err := p.fetchTMDB(ctx, tvURL, &tvDetail); err != nil {
		slog.Error("tmdb get details failed for both movie and tv", "id", id, "error", err)
		return nil, fmt.Errorf("get tmdb details: %w", err)
	}

	ratings, _ := json.Marshal(map[string]float64{
		"tmdb": tvDetail.VoteAverage,
	})

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

	var genreNames []string
	for _, g := range tvDetail.Genres {
		genreNames = append(genreNames, g.Name)
	}

	metadata, _ := json.Marshal(map[string]interface{}{
		"number_of_seasons":  tvDetail.NumberOfSeasons,
		"number_of_episodes": tvDetail.NumberOfEpisodes,
		"genres":             genreNames,
		"first_air_date":     tvDetail.FirstAirDate,
	})

	overview := tvDetail.Overview
	origTitle := tvDetail.OriginalName

	return &MetadataDetails{
		ProviderID:    id,
		Title:         tvDetail.Name,
		OriginalTitle: &origTitle,
		Overview:      &overview,
		Year:          parseTMDBYear(tvDetail.FirstAirDate),
		Ratings:       ratings,
		ExternalIDs:   extIDsJSON,
		Metadata:      metadata,
	}, nil
}

func (p *TMDBProvider) GetImages(ctx context.Context, id string) ([]ImageResult, error) {
	var images []ImageResult

	movieURL := fmt.Sprintf("%s/movie/%s/images?api_key=%s", p.baseURL, id, p.apiKey)
	var movieImages tmdbImagesResponse
	err := p.fetchTMDB(ctx, movieURL, &movieImages)
	if err != nil {
		tvURL := fmt.Sprintf("%s/tv/%s/images?api_key=%s", p.baseURL, id, p.apiKey)
		var tvImages tmdbImagesResponse
		if err := p.fetchTMDB(ctx, tvURL, &tvImages); err != nil {
			return nil, fmt.Errorf("get tmdb images: %w", err)
		}
		movieImages = tvImages
	}

	for _, img := range movieImages.Posters {
		images = append(images, ImageResult{
			URL:    fmt.Sprintf("https://image.tmdb.org/t/p/original%s", img.FilePath),
			Type:   "poster",
			Width:  img.Width,
			Height: img.Height,
		})
	}

	for _, img := range movieImages.Backdrops {
		images = append(images, ImageResult{
			URL:    fmt.Sprintf("https://image.tmdb.org/t/p/original%s", img.FilePath),
			Type:   "backdrop",
			Width:  img.Width,
			Height: img.Height,
		})
	}

	return images, nil
}

// Trending fetches trending items from TMDB for the given media type ("movie" or "tv").
func (p *TMDBProvider) Trending(ctx context.Context, mediaType string, page int) ([]tmdbSearchItem, error) {
	tmdbType := mediaType
	if tmdbType == "series" {
		tmdbType = "tv"
	}
	url := fmt.Sprintf("%s/trending/%s/week?api_key=%s&page=%d", p.baseURL, tmdbType, p.apiKey, page)

	var resp tmdbSearchResponse
	if err := p.fetchTMDB(ctx, url, &resp); err != nil {
		return nil, fmt.Errorf("fetch trending: %w", err)
	}
	return resp.Results, nil
}

// Popular fetches popular items from TMDB for the given media type ("movie" or "tv").
func (p *TMDBProvider) Popular(ctx context.Context, mediaType string, page int) ([]tmdbSearchItem, error) {
	tmdbType := mediaType
	if tmdbType == "series" {
		tmdbType = "tv"
	}
	url := fmt.Sprintf("%s/%s/popular?api_key=%s&page=%d", p.baseURL, tmdbType, p.apiKey, page)

	var resp tmdbSearchResponse
	if err := p.fetchTMDB(ctx, url, &resp); err != nil {
		return nil, fmt.Errorf("fetch popular: %w", err)
	}
	return resp.Results, nil
}

// GetMovieDetails fetches full movie details from TMDB including credits and external IDs.
func (p *TMDBProvider) GetMovieDetails(ctx context.Context, id string) (*TMDBFullDetail, error) {
	url := fmt.Sprintf("%s/movie/%s?api_key=%s&append_to_response=external_ids,credits", p.baseURL, id, p.apiKey)

	var detail TMDBFullDetail
	if err := p.fetchTMDB(ctx, url, &detail); err != nil {
		return nil, fmt.Errorf("fetch movie details: %w", err)
	}
	return &detail, nil
}

// GetTVDetails fetches full TV show details from TMDB including credits and external IDs.
func (p *TMDBProvider) GetTVDetails(ctx context.Context, id string) (*TMDBFullDetail, error) {
	url := fmt.Sprintf("%s/tv/%s?api_key=%s&append_to_response=external_ids,credits", p.baseURL, id, p.apiKey)

	var detail TMDBFullDetail
	if err := p.fetchTMDB(ctx, url, &detail); err != nil {
		return nil, fmt.Errorf("fetch tv details: %w", err)
	}
	return &detail, nil
}
