package tmdb

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

	"encoding/json"
	"net/http"
	"sort"
	"time"
	"fmt"

	"notabug.org/apiote/gott"
)

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 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
}

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", 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 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).
		Finish()

	if err != nil {
		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
	}
}
