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"
	"sort"
	"time"

	"notabug.org/apiote/gott"
)

const (
	PREMIERE_TYPE_PREMIERE       int = 1
	PREMIERE_TYPE_CINEMA_LIMITED     = 2
	PREMIERE_TYPE_CINEMA             = 3
	PREMIERE_TYPE_DIGITAL            = 4
	PREMIERE_TYPE_PHYSICAL           = 5
	PREMIERE_TYPE_TV                 = 6
)

type NotFountError struct {
	User         string
	ItemType     string
	ItemID       string
	IsOnWantList bool
}

func (e NotFountError) Error() string {
	return e.ItemType + ": " + e.ItemID + " not found"
}

type Collection struct {
	Id    int
	Name  string
	Parts []struct {
		Id               int
		Release_date_str string `json:"release_date"`
		Release_date     time.Time
		Poster_path      string `json:"poster_path"`
		Title            string
		IsWatched        bool
	}
}

type ReleaseDate struct {
	Release_date_str string `json:"release_date"`
	ReleaseDate      time.Time
	Type             int
	Note             string
	Locale           string `json:"iso_639_1"`
}

type ReleaseDateRaw struct {
	Results []struct {
		Country string        `json:"iso_3166_1"`
		Dates   []ReleaseDate `json:"release_dates"`
	}
}

type Film struct {
	Id            int
	Etag          string
	Backdrop_path string     `json:"backdrop_path"`
	Collection    Collection `json:"belongs_to_collection"`
	Genres        []struct {
		Id   int
		Name string
	}
	Original_title       string `json:"original_title"`
	Overview             string
	Poster_path          string `json:"poster_path"`
	Release_date_str     string `json:"release_date"`
	Release_date         time.Time
	Runtime              int
	Status               string
	Tagline              string
	Title                string
	Vote_count           int     `json:"vote_count"`
	Vote_average         float32 `json:"vote_average"`
	Source               string
	Credits              ShowCredits
	BasedOn              datastructure.Book
	Experiences          []time.Time
	IsOnWantList         bool
	ReleaseDatesRaw      ReleaseDateRaw `json:"release_dates"`
	ReleaseDates         map[string][]ReleaseDate
	EarliestReleaseDates map[int]ReleaseDate
}

func (f *Film) GetItemInfo() datastructure.ItemInfo {
	part := 0
	for i, p := range f.Collection.Parts {
		if p.Title == f.Title {
			part = i
			break
		}
	}
	genres := ""
	for _, genre := range f.Genres {
		genres += fmt.Sprintf("%d", genre.Id) + ","
	}

	itemInfo := datastructure.ItemInfo{
		Cover:     f.Poster_path,
		Status:    f.Status,
		Title:     f.Original_title,
		YearStart: f.Release_date.Year(),
		// todo BasedOn:
		Genres:     genres,
		Runtime:    f.Runtime,
		Collection: f.Collection.Id,
		Part:       part,
	}
	return itemInfo
}

func (f *Film) GetItemType() datastructure.ItemType {
	return datastructure.ItemTypeFilm
}

func (f *Film) AddBasedOn(book datastructure.Book) {
	f.BasedOn = book
}

func (f *Film) SetOnWantList(isOnList bool) {
	f.IsOnWantList = isOnList
}

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

func (f Film) GetLastExperienceFull(strings i18n.Translation) string {
	return i18n.FormatDate(f.Experiences[0], strings.Global["date_format_full"], strings.Global)
}

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

func unmarshalFilm(args ...interface{}) (interface{}, error) {
	id := args[0].(*network.Request).Id
	result := args[1].(*network.Result)
	film := &Film{}
	err := json.Unmarshal(result.Body, film)
	film.Source = "https://www.themoviedb.org/movie/" + id
	film.Etag = result.Etag
	result.Result = film
	return gott.Tuple(args), err
}

func convertFilmDate(args ...interface{}) (interface{}, error) {
	result := args[1].(*network.Result)
	film := result.Result.(*Film)
	if film.Release_date_str != "" {
		date, err := time.Parse("2006-01-02", film.Release_date_str)
		film.Release_date = date
		result.Result = film
		return gott.Tuple(args), err
	}
	return gott.Tuple(args), nil
}

func convertReleaseDates(args ...interface{}) (interface{}, error) {
	result := args[1].(*network.Result)
	film := result.Result.(*Film)

	releaseDates := map[string][]ReleaseDate{}
	for _, countryDate := range film.ReleaseDatesRaw.Results {
		for _, releaseDate := range countryDate.Dates {
			if releaseDate.Release_date_str != "" {
				date, err := time.Parse("2006-01-02T00:00:00.000Z", releaseDate.Release_date_str)
				if err != nil {
					return gott.Tuple(args), err
				}

				locale := ""
				if releaseDate.Locale == "" {
					locale = countryDate.Country
				} else {
					// todo check if locale is correct iso locale code
					locale = releaseDate.Locale + "_" + countryDate.Country
				}

				releaseDates[locale] = append(releaseDates[locale], ReleaseDate{
					Type:        releaseDate.Type,
					Note:        releaseDate.Note,
					Locale:      locale,
					ReleaseDate: date,
				})
			}
		}
	}

	// todo sort in locale by date
	// todo sort locales by country name

	film.ReleaseDates = releaseDates
	film.ReleaseDatesRaw = ReleaseDateRaw{}
	return gott.Tuple(args), nil
}

func filterEarliestReleases(args ...interface{}) interface{} {
	result := args[1].(*network.Result)
	film := result.Result.(*Film)
	film.EarliestReleaseDates = map[int]ReleaseDate{}
	earliestReleaseDates := map[int]ReleaseDate{}

	for _, countryDates := range film.ReleaseDates {
		for _, rd := range countryDates {
			if earliestReleaseDates[rd.Type].ReleaseDate.After(rd.ReleaseDate) || earliestReleaseDates[rd.Type].ReleaseDate.IsZero() {
				earliestReleaseDates[rd.Type] = rd
			}
		}
	}
	film.EarliestReleaseDates = earliestReleaseDates
	return gott.Tuple(args)
}

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

func unmarshalCollection(args ...interface{}) (interface{}, error) {
	result := args[1].(*network.Result)
	collection := &Collection{}
	err := json.Unmarshal(result.Body, collection)
	result.Result = collection
	return gott.Tuple(args), err
}

func convertCollectionDates(args ...interface{}) (interface{}, error) {
	collection := args[1].(*network.Result).Result.(*Collection)
	for i, part := range collection.Parts {
		if part.Release_date_str != "" {
			date, err := time.Parse("2006-01-02", part.Release_date_str)
			if err != nil {
				return gott.Tuple(args), err
			}
			part.Release_date = date
			collection.Parts[i] = part
		}
	}
	return gott.Tuple(args), nil
}

func sortCollection(args ...interface{}) interface{} {
	collection := args[1].(*network.Result).Result.(*Collection)
	sort.Slice(collection.Parts, func(i, j int) bool {
		isBefore := collection.Parts[i].Release_date.Before(collection.Parts[j].Release_date)
		return (isBefore && !collection.Parts[i].Release_date.IsZero()) ||
			(!isBefore && collection.Parts[j].Release_date.IsZero())
	})
	return gott.Tuple(args)
}

func GetFilm(id, language string) (*Film, error) {
	film, err := gott.
		NewResult(gott.Tuple{&network.Request{Id: id, Language: language}, &network.Result{}}).
		Bind(createFilmRequest).
		Bind(getCacheEntry).
		Map(network.AddHeaders).
		Bind(network.DoRequest).
		Bind(network.HandleRequestError).
		Bind(network.ReadResponse).
		Tee(cleanCache).
		Tee(saveCacheEntry).
		Bind(unmarshalFilm).
		Bind(convertFilmDate).
		Bind(convertReleaseDates).
		Map(filterEarliestReleases).
		Finish()

	if err != nil {
		if err.Error() == "404" {
			err = NotFountError{
				ItemType: "film",
				ItemID:   id,
			}
		}
		return &Film{}, err
	} else {
		return film.(gott.Tuple)[1].(*network.Result).Result.(*Film), nil
	}
}

func GetCollection(id, language string) (*Collection, error) {
	collection, err := gott.
		NewResult(gott.Tuple{&network.Request{Id: id, Language: language}, &network.Result{}}).
		Bind(createCollectionRequest).
		Bind(getCacheEntry).
		Map(network.AddHeaders).
		Bind(network.DoRequest).
		Bind(network.HandleRequestError).
		Bind(network.ReadResponse).
		Tee(cleanCache).
		Tee(saveCacheEntry).
		Bind(unmarshalCollection).
		Bind(convertCollectionDates).
		Map(sortCollection).
		Finish()

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