package tmdb

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

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

	"notabug.org/apiote/gott"
)

type Credits struct {
	Cast []struct {
		Id                 int
		Media_type         string `json:"media_type"`
		Poster_path        string `json:"poster_path"`
		Backdrop_path      string `json:"backdrop_path"`
		Popularity         float64
		Character          string
		Title              string
		Name               string
		First_air_date_str string `json:"first_air_date"`
		Release_date_str   string `json:"release_date"`
		Release_date       time.Time
	}
	Crew []struct {
		Id                 int
		Media_type         string `json:"media_type"`
		Poster_path        string `json:"poster_path"`
		Job                string
		Title              string
		Name               string
		First_air_date_str string `json:"first_air_date"`
		Release_date_str   string `json:"release_date"`
		Release_date       time.Time
	}
}

type Person struct {
	Etag           string
	Birthday_str   string    `json:"birthday"`
	Birthday       time.Time `json:"-"`
	Deathday_str   string    `json:"deathday"`
	Deathday       time.Time `json:"-"`
	Name           string
	Biography      string
	Place_of_birth string `json:"place_of_birth"`
	Profile_path   string `json:"profile_path"`
	Backdrop_path  string
	Source         string
	Credits        Credits `json:"combined_credits"`
}

func createPersonRequest(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/person/"+request.Id+"?api_key="+config.TmdbApiKey+"&language="+request.Language+"&append_to_response=combined_credits", nil)
	result.Client = client
	result.Request = httpRequest
	return gott.Tuple(args), err
}

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

func convertPersonBirthDate(args ...interface{}) (interface{}, error) {
	person := args[1].(*network.Result).Result.(*Person)
	var (
		date time.Time
		err  error
	)
	if person.Birthday_str != "" {
		date, err = time.Parse("2006-01-02", person.Birthday_str)
		person.Birthday = date
	}
	return gott.Tuple(args), err
}

func convertPersonDeathDate(args ...interface{}) (interface{}, error) {
	person := args[1].(*network.Result).Result.(*Person)
	var (
		date time.Time
		err  error
	)
	if person.Deathday_str != "" {
		date, err = time.Parse("2006-01-02", person.Deathday_str)
		person.Deathday = date
	}
	return gott.Tuple(args), err
}

func convertJobsDates(args ...interface{}) (interface{}, error) {
	person := args[1].(*network.Result).Result.(*Person)
	for i, character := range person.Credits.Cast {
		if character.Release_date_str != "" {
			date, err := time.Parse("2006-01-02", character.Release_date_str)
			character.Release_date = date
			if err != nil {
				return gott.Tuple(args), err
			}
		}
		if character.First_air_date_str != "" {
			date, err := time.Parse("2006-01-02", character.First_air_date_str)
			character.Release_date = date
			if err != nil {
				return gott.Tuple(args), err
			}
		}
		person.Credits.Cast[i] = character
	}
	for i, job := range person.Credits.Crew {
		if job.Release_date_str != "" {
			date, err := time.Parse("2006-01-02", job.Release_date_str)
			job.Release_date = date
			if err != nil {
				return gott.Tuple(args), err
			}
		}
		if job.First_air_date_str != "" {
			date, err := time.Parse("2006-01-02", job.First_air_date_str)
			job.Release_date = date
			if err != nil {
				return gott.Tuple(args), err
			}
		}
		person.Credits.Crew[i] = job
	}
	return gott.Tuple(args), nil
}

func convertJobsNames(args ...interface{}) interface{} {
	person := args[1].(*network.Result).Result.(*Person)
	for i, character := range person.Credits.Cast {
		if character.Title == "" {
			character.Title = character.Name
		}
		person.Credits.Cast[i] = character
	}
	for i, job := range person.Credits.Crew {
		if job.Title == "" {
			job.Title = job.Name
		}
		person.Credits.Crew[i] = job
	}
	return gott.Tuple(args)
}

func sortJobs(args ...interface{}) interface{} {
	person := args[1].(*network.Result).Result.(*Person)
	sort.Slice(person.Credits.Cast, func(i, j int) bool {
		isBefore := person.Credits.Cast[i].Release_date.Before(person.Credits.Cast[j].Release_date)
		return (!isBefore || person.Credits.Cast[i].Release_date.IsZero()) &&
			(isBefore || !person.Credits.Cast[j].Release_date.IsZero())
	})
	sort.Slice(person.Credits.Crew, func(i, j int) bool {
		isBefore := person.Credits.Crew[i].Release_date.Before(person.Credits.Crew[j].Release_date)
		return (!isBefore || person.Credits.Crew[i].Release_date.IsZero()) &&
			(isBefore || !person.Credits.Crew[j].Release_date.IsZero())
	})
	return gott.Tuple(args)
}

func getBackdrop(args ...interface{}) interface{} {
	person := args[1].(*network.Result).Result.(*Person)
	maxPopularity := 0.0
	backdropPath := ""
	for _, f := range person.Credits.Cast {
		if f.Popularity > maxPopularity {
			backdropPath = f.Backdrop_path
			maxPopularity = f.Popularity
		}
	}
	person.Backdrop_path = backdropPath
	return gott.Tuple(args)
}


func GetPerson(id, language, etag string) (*Person, error) {
	person, err := gott.
		NewResult(gott.Tuple{&network.Request{Id: id, Etag: etag, Language: language}, &network.Result{}}).
		Bind(createPersonRequest).
		Map(network.AddHeaders).
		Bind(network.DoRequest).
		Bind(network.HandleRequestError).
		Bind(network.ReadResponse).
		Bind(unmarshalPerson).
		Bind(convertPersonBirthDate).
		Bind(convertPersonDeathDate).
		Bind(convertJobsDates).
		Map(convertJobsNames).
		Map(sortJobs).
		Map(getBackdrop).
		Finish()

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