package service

import (
	"bytes"
	"context"
	"encoding/json"
	"fmt"
	"net/http"
	"strconv"
	"strings"
	"time"
)

type TVDBProvider struct {
	apiKey      string
	baseURL     string
	httpClient  *http.Client
	bearerToken string
	tokenExpiry time.Time
	lastRequest time.Time
}

func NewTVDBProvider(apiKey string) *TVDBProvider {
	return &TVDBProvider{
		apiKey:  apiKey,
		baseURL: "https://api4.thetvdb.com/v4",
		httpClient: &http.Client{
			Timeout: 15 * time.Second,
		},
	}
}

type tvdbLoginRequest struct {
	APIKey string `json:"apikey"`
}

type tvdbLoginResponse struct {
	Token string `json:"token"`
}

type tvdbSearchResponse struct {
	Data []tvdbSearchItem `json:"data"`
}

type tvdbSearchItem struct {
	ID        string `json:"id"`
	Name      string `json:"name"`
	Overview  string `json:"overview"`
	FirstAired string `json:"first_aired"`
	Year      string `json:"year"`
	ImageURL  string `json:"image"`
	Type      string `json:"type"`
}

type tvdbSeriesDetail struct {
	ID               string       `json:"id"`
	Name             string       `json:"name"`
	Overview         string       `json:"overview"`
	FirstAired       string       `json:"firstAired"`
	Status           tvdbStatus   `json:"status"`
	NumberOfSeasons  int          `json:"numberOfSeasons"`
	NumberOfEpisodes int          `json:"numberOfEpisodes"`
	AverageRuntime   int          `json:"averageRuntime"`
	Ratings          []tvdbRating `json:"ratings"`
	Image            string       `json:"image"`
}

type tvdbStatus struct {
	Name string `json:"name"`
}

type tvdbRating struct {
	Name   string  `json:"name"`
	Source string  `json:"source"`
	Rating float64 `json:"rating"`
}

type tvdbEpisodesResponse struct {
	Data []tvdbEpisode `json:"data"`
}

type tvdbEpisode struct {
	ID                string `json:"id"`
	Name              string `json:"name"`
	Overview          string `json:"overview"`
	AiredSeasonNumber int    `json:"airedSeasonNumber"`
	AiredEpisodeNumber int   `json:"airedEpisodeNumber"`
	AbsoluteNumber    int    `json:"absoluteNumber"`
	Runtime           int    `json:"runtime"`
	AiredAt           string `json:"aired"`
	Thumbnail         string `json:"thumbnail"`
}

type tvdbImagesResponse struct {
	Data []tvdbImage `json:"data"`
}

type tvdbImage struct {
	Image     string `json:"image"`
	ImageType string `json:"type"`
	Resolution string `json:"resolution"`
}

func (p *TVDBProvider) rateLimit() {
	elapsed := time.Since(p.lastRequest)
	if elapsed < 250*time.Millisecond {
		time.Sleep(250*time.Millisecond - elapsed)
	}
	p.lastRequest = time.Now()
}

func (p *TVDBProvider) authenticate(ctx context.Context) error {
	if p.bearerToken != "" && time.Now().Before(p.tokenExpiry) {
		return nil
	}

	p.rateLimit()

	loginReq := tvdbLoginRequest{APIKey: p.apiKey}
	body, err := json.Marshal(loginReq)
	if err != nil {
		return fmt.Errorf("marshal login request: %w", err)
	}

	req, err := http.NewRequestWithContext(ctx, http.MethodPost, p.baseURL+"/login", bytes.NewReader(body))
	if err != nil {
		return fmt.Errorf("create login request: %w", err)
	}
	req.Header.Set("Content-Type", "application/json")

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

	if resp.StatusCode != http.StatusOK {
		return fmt.Errorf("tvdb login failed: status %d", resp.StatusCode)
	}

	var loginResp tvdbLoginResponse
	if err := json.NewDecoder(resp.Body).Decode(&loginResp); err != nil {
		return fmt.Errorf("decode login response: %w", err)
	}

	p.bearerToken = loginResp.Token
	p.tokenExpiry = time.Now().Add(24 * time.Hour)
	return nil
}

func (p *TVDBProvider) fetchTVDB(ctx context.Context, url string, result interface{}) error {
	if err := p.authenticate(ctx); err != nil {
		return fmt.Errorf("tvdb auth: %w", err)
	}

	p.rateLimit()

	req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
	if err != nil {
		return fmt.Errorf("create request: %w", err)
	}
	req.Header.Set("Authorization", "Bearer "+p.bearerToken)

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

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

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

	return nil
}

func (p *TVDBProvider) Name() string {
	return "tvdb"
}

func (p *TVDBProvider) Search(ctx context.Context, query string, opts SearchOptions) ([]MetadataSearchResult, error) {
	url := fmt.Sprintf("%s/search?query=%s&type=series", p.baseURL, query)

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

	var results []MetadataSearchResult
	for _, item := range resp.Data {
		var year *int
		if item.FirstAired != "" && len(item.FirstAired) >= 4 {
			if y, err := strconv.Atoi(item.FirstAired[:4]); err == nil {
				year = &y
			}
		}

		results = append(results, MetadataSearchResult{
			ProviderID: item.ID,
			Title:      item.Name,
			Year:       year,
			MediaType:  "series",
			Overview:   item.Overview,
		})
	}

	return results, nil
}

func (p *TVDBProvider) GetDetails(ctx context.Context, id string) (*MetadataDetails, error) {
	seriesURL := fmt.Sprintf("%s/series/%s", p.baseURL, id)

	var detail tvdbSeriesDetail
	if err := p.fetchTVDB(ctx, seriesURL, &detail); err != nil {
		return nil, fmt.Errorf("get tvdb series: %w", err)
	}

	var year *int
	if detail.FirstAired != "" && len(detail.FirstAired) >= 4 {
		if y, err := strconv.Atoi(detail.FirstAired[:4]); err == nil {
			year = &y
		}
	}

	extIDs, _ := json.Marshal(map[string]string{"tvdb": id})

	ratingsMap := make(map[string]float64)
	for _, r := range detail.Ratings {
		ratingsMap[r.Source] = r.Rating
	}
	ratings, _ := json.Marshal(ratingsMap)

	episodesURL := fmt.Sprintf("%s/series/%s/episodes", p.baseURL, id)
	var episodesResp tvdbEpisodesResponse
	var episodeList []map[string]interface{}
	if err := p.fetchTVDB(ctx, episodesURL, &episodesResp); err == nil {
		for _, ep := range episodesResp.Data {
			episodeList = append(episodeList, map[string]interface{}{
				"season":         ep.AiredSeasonNumber,
				"episode":        ep.AiredEpisodeNumber,
				"absolute_number": ep.AbsoluteNumber,
				"title":          ep.Name,
				"aired_date":     ep.AiredAt,
				"runtime":        ep.Runtime,
			})
		}
	}

	metadata, _ := json.Marshal(map[string]interface{}{
		"number_of_seasons":  detail.NumberOfSeasons,
		"number_of_episodes": detail.NumberOfEpisodes,
		"average_runtime":    detail.AverageRuntime,
		"status":             detail.Status.Name,
		"episodes":           episodeList,
	})

	overview := detail.Overview

	return &MetadataDetails{
		ProviderID:  id,
		Title:       detail.Name,
		Overview:    &overview,
		Year:        year,
		Ratings:     ratings,
		ExternalIDs: extIDs,
		Metadata:    metadata,
	}, nil
}

func (p *TVDBProvider) GetImages(ctx context.Context, id string) ([]ImageResult, error) {
	imagesURL := fmt.Sprintf("%s/series/%s/images", p.baseURL, id)

	var resp tvdbImagesResponse
	if err := p.fetchTVDB(ctx, imagesURL, &resp); err != nil {
		return nil, fmt.Errorf("get tvdb images: %w", err)
	}

	var images []ImageResult
	for _, img := range resp.Data {
		imgURL := img.Image
		if !strings.HasPrefix(imgURL, "http") {
			imgURL = "https://artworks.thetvdb.com" + imgURL
		}

		imgType := img.ImageType
		switch imgType {
		case "poster":
			imgType = "poster"
		case "fanart":
			imgType = "backdrop"
		case "season":
			imgType = "poster"
		case "series":
			imgType = "poster"
		}

		images = append(images, ImageResult{
			URL:  imgURL,
			Type: imgType,
		})
	}

	return images, nil
}
