package tmdb

import (
	"notabug.org/apiote/gott"

	"notabug.org/apiote/amuse/utils"

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

type Credits struct {
	Cast []struct {
		Id                 int
		Media_type         string `json:"media_type"`
		Poster_path        string `json:"poster_path"`
		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"`
	Credits        Credits `json:"combined_credits"`
}

func createPersonRequest(args ...interface{}) (interface{}, error) {
	tmdbRequest := args[0].(TmdbRequest)
	client := &http.Client{}
	request, err := http.NewRequest("GET", "https://api.themoviedb.org/3/person/"+tmdbRequest.id+"?api_key="+API_KEY+"&language="+tmdbRequest.language+"&append_to_response=combined_credits", nil)
	args[1] = client
	args[2] = request
	return gott.Tuple(args), err
}

func unmarshalPerson(args ...interface{}) (interface{}, error) {
	body := []byte(args[3].(string))
	person := &Person{}
	err := json.Unmarshal(body, person)
	person.Etag = args[4].(string)
	args[3] = person
	return gott.Tuple(args), err
}

func convertPersonDates(args ...interface{}) (interface{}, error) {
	person := args[3].(*Person)
	var err error
	if person.Birthday_str != "" {
		date, err2 := time.Parse("2006-01-02", person.Birthday_str)
		person.Birthday = date
		err = utils.Or(err, err2)
	}
	if person.Deathday_str != "" {
		date, err2 := time.Parse("2006-01-02", person.Deathday_str)
		person.Deathday = date
		err = utils.Or(err, err2)
	}
	args[3] = person
	return gott.Tuple(args), err
}

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

func convertJobsNames(args ...interface{}) interface{} {
	person := args[3].(*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
	}
	args[3] = person
	return gott.Tuple(args)
}

func sortJobs(args ...interface{}) interface{} {
	person := args[3].(*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.Year() == 1) &&
			(isBefore || person.Credits.Cast[j].Release_date.Year() != 1)
	})
	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.Year() == 1) &&
			(isBefore || person.Credits.Crew[j].Release_date.Year() != 1)
	})
	args[3] = person
	return gott.Tuple(args)
}

func GetPerson(id, language, etag string) (*Person, error) {
	person, err := gott.
		NewResult(gott.Tuple{TmdbRequest{id, etag, language, ""}, nil, nil, nil, nil}).
		Bind(createPersonRequest).
		Bind(doRequest).
		Bind(handleRequestError).
		Bind(readResponse).
		Bind(unmarshalPerson).
		Bind(convertPersonDates).
		Bind(convertJobsDates).
		Map(convertJobsNames).
		Map(sortJobs).
		Finish()

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