package tmdb

import (
	"notabug.org/apiote/amuse/config"
	"notabug.org/apiote/amuse/datastructure"
	"notabug.org/apiote/amuse/i18n"
	"notabug.org/apiote/amuse/network"

	"encoding/json"
	"fmt"
	"net/http"
	"strconv"
	"strings"
	"time"

	"notabug.org/apiote/gott"
)

type Episode struct {
	Air_date_str     string `json:"air_date"`
	Air_date         time.Time
	Episode_number   int `json:"episode_number"`
	Name             string
	Overview         string
	Season_number    int     `json:"season_number"`
	Still_path       string  `json:"still_path"`
	Vote_count       int     `json:"vote_count"`
	Vote_average     float32 `json:"vote_average"`
	Episode_code     string
	Experiences      []time.Time
	ContainsSpoilers bool
}

func (e Episode) IsWatched() bool {
	return len(e.Experiences) > 0 && !e.Experiences[0].IsZero()
}

func (e Episode) GetLastExperience(strings i18n.Translation, timezone string) string {
	return i18n.FormatDateNice(e.Experiences[0], strings, timezone)
}

type Season struct {
	Season_number        int `json:"season_number"`
	Season_number_string string
	Episodes             []Episode
	Credits              ShowCredits
}

type TvSerie struct {
	Id                 string `json:"-"`
	Etag               string
	Backdrop_path      string `json:"backdrop_path"`
	Episode_run_time   []int  `json:"episode_run_time"`
	First_air_date_str string `json:"first_air_date"`
	First_air_date     time.Time
	Genres             []struct {
		Id   int
		Name string
	}
	Last_air_date_str     string `json:"last_air_date"`
	Last_air_date         time.Time
	Last_episode_to_air   Episode `json:"last_episode_to_air"`
	Name                  string
	Number_of_episodes    int    `json:"number_of_episodes"`
	Original_name         string `json:"original_name"`
	Overview              string
	Poster_path           string `json:"poster_path"`
	Seasons               []Season
	Status                string
	Source                string
	Type                  string
	Vote_count            int     `json:"vote_count"`
	Vote_average          float32 `json:"vote_average"`
	Credits               ShowCredits
	BasedOn               datastructure.Book
	IsOnWantList          bool
	Next_episode_to_watch Episode
	Progress              int
	WatchedEpisodes       int
	AllEpisodes           int
	SkippedEpisodes       int
}

func (s *TvSerie) GetItemInfo() datastructure.ItemInfo {
	genres := ""
	for _, genre := range s.Genres {
		genres += fmt.Sprintf("%d", genre.Id) + ","
	}

	years := strings.Split(s.GetYears(), "–")
	var (
		yearStart int64 = 0
		yearEnd   int64 = 0
	)

	yearStart, _ = strconv.ParseInt(years[0], 10, 32)
	if len(years) > 1 {
		yearEnd, _ = strconv.ParseInt(years[1], 10, 32)
	}

	episodes := 0
	for _, season := range s.Seasons {
		episodes += len(season.Episodes)
	}

	itemInfo := datastructure.ItemInfo{
		Cover:     s.Poster_path,
		Status:    s.Status,
		Title:     s.Original_name,
		YearStart: int(yearStart),
		YearEnd:   int(yearEnd),
		Genres:    genres,
		Episodes:  episodes,
		// BasedOn
	}

	return itemInfo
}

func (s *TvSerie) GetItemType() datastructure.ItemType {
	return datastructure.ItemTypeTvserie
}

func (s *TvSerie) AddBasedOn(book datastructure.Book) {
	s.BasedOn = book
}

func (s *TvSerie) SetOnWantList(isOnList bool) {
	s.IsOnWantList = isOnList
}

func (s TvSerie) GetYears() string {
	if s.First_air_date.IsZero() {
		return ""
	} else if s.Status == "Ended" || s.Status == "Canceled" {
		if s.Last_air_date.Year() == s.First_air_date.Year() || s.Last_air_date.IsZero() {
			return strconv.FormatInt(int64(s.First_air_date.Year()), 10)
		} else {
			return strconv.FormatInt(int64(s.First_air_date.Year()), 10) + "–" + strconv.FormatInt(int64(s.Last_air_date.Year()), 10)
		}
	} else {
		return strconv.FormatInt(int64(s.First_air_date.Year()), 10) + "–"
	}
}

func createSerieRequest(args ...interface{}) (interface{}, error) {
	request := args[0].(*network.Request)
	result := args[1].(*network.Result)
	client := &http.Client{}
	httpRequest, err := http.NewRequest("GET", "https://api.themoviedb.org/3/tv/"+request.Id+"?api_key="+config.TmdbApiKey+"&language="+request.Language+"&append_to_response=credits", nil)
	result.Client = client
	result.Request = httpRequest
	return gott.Tuple(args), err
}

func unmarshalSerie(args ...interface{}) (interface{}, error) {
	request := args[0].(*network.Request)
	result := args[1].(*network.Result)
	serie := &TvSerie{}
	err := json.Unmarshal(result.Body, serie)
	serie.Id = request.Id
	serie.Source = "https://themoviedb.org/tv/" + request.Id
	result.Result = serie
	return gott.Tuple(args), err
}

func convertFirstDate(args ...interface{}) (interface{}, error) {
	serie := args[1].(*network.Result).Result.(*TvSerie)
	var (
		date time.Time
		err  error
	)
	if serie.First_air_date_str != "" {
		date, err = time.Parse("2006-01-02", serie.First_air_date_str)
		serie.First_air_date = date
	}
	return gott.Tuple(args), err
}

func convertLatestDate(args ...interface{}) (interface{}, error) {
	serie := args[1].(*network.Result).Result.(*TvSerie)
	var (
		date time.Time
		err  error
	)
	if serie.Last_air_date_str != "" {
		date, err = time.Parse("2006-01-02", serie.Last_air_date_str)
		serie.Last_air_date = date
	}
	return gott.Tuple(args), err
}

func convertLastDate(args ...interface{}) (interface{}, error) {
	serie := args[1].(*network.Result).Result.(*TvSerie)
	var (
		date time.Time
		err  error
	)
	if serie.Last_episode_to_air.Air_date_str != "" {
		date, err = time.Parse("2006-01-02", serie.Last_episode_to_air.Air_date_str)
		serie.Last_episode_to_air.Air_date = date
	}
	return gott.Tuple(args), err
}

func createSeasonRequest(args ...interface{}) (interface{}, error) {
	request := args[0].(*network.Request)
	result := args[1].(*network.Result)
	client := &http.Client{}
	httpRequest, err := http.NewRequest("GET", "https://api.themoviedb.org/3/tv/"+request.Id+"/season/"+request.Subid+"?api_key="+config.TmdbApiKey+"&language="+request.Language+"&append_to_response=credits", nil)
	result.Client = client
	result.Request = httpRequest
	return gott.Tuple(args), err
}

func unmarshalSeason(args ...interface{}) (interface{}, error) {
	result := args[1].(*network.Result)
	season := &Season{}
	err := json.Unmarshal(result.Body, season)
	for i, episode := range season.Episodes {
		episode.Episode_code = fmt.Sprintf("S%02dE%02d", season.Season_number, episode.Episode_number)
		season.Episodes[i] = episode
	}
	season.Season_number_string = fmt.Sprintf("%02d", season.Season_number)
	result.Result = season
	return gott.Tuple(args), err
}

func convertSeasonDates(args ...interface{}) (interface{}, error) {
	season := args[1].(*network.Result).Result.(*Season)
	for i, episode := range season.Episodes {
		if episode.Air_date_str != "" {
			date, err := time.Parse("2006-01-02", episode.Air_date_str)
			if err != nil {
				return gott.Tuple(args), err
			}
			episode.Air_date = date
			season.Episodes[i] = episode
		}
	}
	return gott.Tuple(args), nil
}

func findLatestEpisodeCode(args ...interface{}) interface{} {
	serie := args[1].(*network.Result).Result.(*TvSerie)
	serie.Last_episode_to_air.Episode_code = fmt.Sprintf("S%02dE%02d",
		serie.Last_episode_to_air.Season_number, serie.Last_episode_to_air.Episode_number)
	return gott.Tuple(args)
}

func GetSerie(id, language string) (*TvSerie, error) {
	serie, err := gott.
		NewResult(gott.Tuple{&network.Request{Id: id, Language: language}, &network.Result{}}).
		Bind(createSerieRequest).
		Bind(getCacheEntry).
		Map(network.AddHeaders).
		Bind(network.DoRequest).
		Bind(network.HandleRequestError).
		Bind(network.ReadResponse).
		Tee(cleanCache).
		Tee(saveCacheEntry).
		Bind(unmarshalSerie).
		Bind(convertFirstDate).
		Bind(convertLatestDate).
		Bind(convertLastDate).
		Map(findLatestEpisodeCode).
		Finish()

	if err != nil {
		return &TvSerie{}, err
	} else {
		return serie.(gott.Tuple)[1].(*network.Result).Result.(*TvSerie), nil
	}
}

func GetSeason(serie *TvSerie, language string, seasonNumber int) (Season, error) {
	seasonNumberS := strconv.FormatInt(int64(seasonNumber), 10)
	s, err := gott.
		NewResult(gott.Tuple{&network.Request{Id: serie.Id, Language: language, Subid: seasonNumberS}, &network.Result{}}).
		Bind(createSeasonRequest).
		Bind(getCacheEntry).
		Map(network.AddHeaders).
		Bind(network.DoRequest).
		Bind(network.HandleRequestError).
		Bind(network.ReadResponse).
		Tee(cleanCache).
		Tee(saveCacheEntry).
		Bind(unmarshalSeason).
		Bind(convertSeasonDates).
		Finish()
	season := *s.(gott.Tuple)[1].(*network.Result).Result.(*Season)
	return season, err
}

func GetSeasons(serie *TvSerie, language string) ([]Season, error) {
	var seasons []Season
	for _, serieSeason := range serie.Seasons {
		season, err := GetSeason(serie, language, serieSeason.Season_number)
		if err != nil {
			return seasons, err
		}
		seasons = append(seasons, season)
	}
	return seasons, nil
}
