package libamuse

import (
	"notabug.org/apiote/amuse/accounts"
	"notabug.org/apiote/amuse/datastructure"
	"notabug.org/apiote/amuse/db"
	"notabug.org/apiote/amuse/tmdb"

	"fmt"
	"sort"
	"strings"
	"time"

	"notabug.org/apiote/gott"
)

func min(a, b int) int {
	if a > b {
		return b
	} else {
		return a
	}
}

func getTvSerie(args ...interface{}) (interface{}, error) {
	data := args[0].(*RequestData)
	result := args[1].(*Result)
	languages := result.languages
	tvSerie, err := tmdb.GetSerie(data.id, languages[0].String())
	result.result = tvSerie
	return gott.Tuple(args), err
}

func getSeasons(args ...interface{}) (interface{}, error) {
	result := args[1].(*Result)
	tvSerie := result.result.(*tmdb.TvSerie)
	languages := result.languages
	seasons, err := tmdb.GetSeasons(tvSerie, languages[0].String())
	tvSerie.Seasons = seasons
	return gott.Tuple(args), err
}

func getSeason(args ...interface{}) (interface{}, error) {
	result := args[1].(*Result)
	itemId := args[3].(string)
	id := strings.Split(itemId, "/")
	var seasonNumber int
	fmt.Sscanf(id[1][1:3], "%d", &seasonNumber)
	tvSerie := result.result.(*tmdb.TvSerie)
	languages := result.languages
	season, err := tmdb.GetSeason(tvSerie, languages[0].String(), seasonNumber)
	tvSerie.Seasons = make([]tmdb.Season, seasonNumber+1)
	tvSerie.Seasons[seasonNumber] = season
	return gott.Tuple(args), err
}

func mergeCredits(args ...interface{}) (interface{}, error) {
	result := args[1].(*Result)
	mergedCast := map[string]tmdb.ShowCastEntry{}
	mergedCrew := map[string]tmdb.ShowCrewEntry{}

	tvSerie := result.result.(*tmdb.TvSerie)
	for _, person := range tvSerie.Credits.Cast {
		mergedCast[person.Name+person.Character] = person
	}
	for _, season := range tvSerie.Seasons {
		for _, person := range season.Credits.Cast {
			mergedCast[person.Name+person.Character] = person
		}
	}

	for _, person := range tvSerie.Credits.Crew {
		mergedCrew[person.Name+person.Job] = person
	}
	for _, season := range tvSerie.Seasons {
		for _, person := range season.Credits.Crew {
			mergedCrew[person.Name+person.Job] = person
		}
	}

	tvSerie.Credits.Cast = []tmdb.ShowCastEntry{}
	for _, value := range mergedCast {
		tvSerie.Credits.Cast = append(tvSerie.Credits.Cast, value)
	}
	sort.Slice(tvSerie.Credits.Cast, func(i, j int) bool {
		return tvSerie.Credits.Cast[i].Character < tvSerie.Credits.Cast[j].Character
	})

	tvSerie.Credits.Crew = []tmdb.ShowCrewEntry{}
	for _, value := range mergedCrew {
		tvSerie.Credits.Crew = append(tvSerie.Credits.Crew, value)
	}
	sort.Slice(tvSerie.Credits.Crew, func(i, j int) bool {
		return tvSerie.Credits.Crew[i].Job < tvSerie.Credits.Crew[j].Job
	})

	return gott.Tuple(args), nil
}

func renderSerie(args ...interface{}) interface{} {
	result := args[1].(*Result)
	tvSerie := result.result.(*tmdb.TvSerie)
	result.page = result.renderer.RenderTvSerie(tvSerie, result.languages)
	return gott.Tuple(args)
}

func countAllEpisodes(args ...interface{}) interface{} {
	result := args[1].(*Result)
	tvSerie := result.result.(*tmdb.TvSerie)
	for _, season := range tvSerie.Seasons {
		tvSerie.AllEpisodes += len(season.Episodes)
	}
	return gott.Tuple(args)
}

func calculateProgress(args ...interface{}) (interface{}, error) {
	result := args[1].(*Result)
	tvSerie := result.result.(*tmdb.TvSerie)
	if result.user.IsEmpty() {
		return gott.Tuple(args), nil
	}

	experiences, err := db.GetItemExperiences(result.user.Username, tvSerie.Id, datastructure.ItemTypeTvserie)
	var (
		watched           int
		skipped           int
		watchedAndSkipped int
	)
	for _, e := range experiences {
		isWatched := false
		for _, t := range e {
			if !t.IsZero() {
				isWatched = true
				break
			}
		}
		if isWatched {
			watched += 1
		} else {
			skipped += 1
		}
		watchedAndSkipped += 1
	}
	if tvSerie.AllEpisodes > 0 {
		tvSerie.Progress = min(watched*100/(tvSerie.AllEpisodes-skipped), 100)
	}
	tvSerie.WatchedEpisodes = watched
	tvSerie.SkippedEpisodes = skipped
	return gott.Tuple(args), err
}

func findNextEpisode(args ...interface{}) (interface{}, error) {
	result := args[1].(*Result)
	tvSerie := result.result.(*tmdb.TvSerie)
	if result.user.IsEmpty() {
		return gott.Tuple(args), nil
	}
	experiences, err := db.GetItemExperiences(result.user.Username, tvSerie.Id, datastructure.ItemTypeTvserie)

	for _, season := range tvSerie.Seasons {
		for _, episode := range season.Episodes {
			id := tvSerie.Id + "/" + episode.Episode_code
			if len(experiences[id]) == 0 {
				tvSerie.Next_episode_to_watch = episode
				return gott.Tuple(args), err
			}
		}
	}

	return gott.Tuple(args), err
}

func setSpoilers(args ...interface{}) interface{} {
	result := args[1].(*Result)
	tvSerie := result.result.(*tmdb.TvSerie)
	nextEpisode := tvSerie.Next_episode_to_watch
	nextCode := nextEpisode.Episode_code
	if nextCode == "" {
		if tvSerie.WatchedEpisodes > 0 {
			nextCode = "S99E99"
		} else {
			nextCode = "S01E01"
		}
	}
	nextDate := nextEpisode.Air_date
	if nextDate.IsZero() {
		if tvSerie.WatchedEpisodes > 0 {
			nextDate = time.Now()
		} else {
			for _, season := range tvSerie.Seasons {
				if season.Season_number == 1 && len(season.Episodes) > 0 {
					nextDate = season.Episodes[0].Air_date
				}
			}
		}
	}
	for s := 0; s < len(tvSerie.Seasons); s++ {
		for e := 0; e < len(tvSerie.Seasons[s].Episodes); e++ {
			episode := &(tvSerie.Seasons[s].Episodes[e])
			if episode.Air_date.After(nextDate) ||
				(episode.Air_date == nextDate && episode.Episode_code > nextCode) {
				episode.ContainsSpoilers = true
			}
		}
	}
	episode := &(tvSerie.Last_episode_to_air)
	if episode.Air_date.After(nextDate) ||
		(episode.Air_date == nextDate && episode.Episode_code > nextCode) {
		episode.ContainsSpoilers = true
	}
	return gott.Tuple(args)
}

func getEpisodesExperiences(args ...interface{}) (interface{}, error) {
	result := args[1].(*Result)
	tvSerie := result.result.(*tmdb.TvSerie)
	if result.user.IsEmpty() {
		return gott.Tuple(args), nil
	}
	experiences, err := db.GetItemExperiences(result.user.Username, tvSerie.Id, datastructure.ItemTypeTvserie)

	for s, season := range tvSerie.Seasons {
		for e, episode := range season.Episodes {
			id := tvSerie.Id + "/" + episode.Episode_code
			tvSerie.Seasons[s].Episodes[e].Experiences = experiences[id]
		}
	}

	return gott.Tuple(args), err
}

func ShowTvSerie(id, etag, language, mimetype string, auth accounts.Authentication) (string, error) {
	auth.Necessary = false
	request := &RequestData{id: id, language: language, mimetype: mimetype, auth: auth}
	r, err := gott.
		NewResult(gott.Tuple{request, &Result{}}).
		Bind(parseLanguage).
		Bind(verifyToken).
		Bind(getTvSerie).
		Bind(getSeasons).
		Bind(getBasedOn).
		Bind(mergeCredits).
		Bind(isOnWantList).
		Bind(updateCache).
		Map(countAllEpisodes).
		Bind(calculateProgress).
		Bind(findNextEpisode).
		Bind(getEpisodesExperiences).
		Map(setSpoilers).
		Bind(createRenderer).
		Map(renderSerie).
		Finish()

	if err != nil {
		return "", err
	} else {
		return r.(gott.Tuple)[1].(*Result).page, nil
	}
}
