package libamuse

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

	"errors"
	"fmt"
	"os"
	"strings"
	"time"

	"notabug.org/apiote/gott"
)

func VerifyAuthToken(token accounts.Authentication) (accounts.User, error) {
	if token.Token == "" {
		return accounts.User{}, accounts.AuthError{Err: errors.New("401")}
	}
	session, err := db.GetSession(token.Token)
	if err != nil {
		if _, ok := err.(db.EmptyError); ok {
			err = accounts.AuthError{Err: err}
		}
		fmt.Fprintf(os.Stderr, "Get session err: %v", err)
		return accounts.User{}, err
	}
	now := time.Now()
	if session.Expiry.Before(now) {
		return accounts.User{}, accounts.AuthError{Err: errors.New("Session expired")}
	}
	dbUser, err := db.GetUser(session.Username)
	if err != nil {
		if _, ok := err.(db.EmptyError); ok {
			err = accounts.AuthError{Err: err}
		}
		fmt.Fprintf(os.Stderr, "Get user err: %v", err)
		return accounts.User{}, err
	}
	user := accounts.User{
		Username:     dbUser.Username,
		IsAdmin:      dbUser.IsAdmin,
		Session:      token.Token,
		Timezone:     dbUser.Timezone,
		Country:      dbUser.Country,
		AutoCalendar: dbUser.AutoCalendar,
		Language:     dbUser.Language,
	}
	return user, nil
}

func addToWantlist(args ...interface{}) (interface{}, error) {
	data := args[0].(*RequestData)
	result := args[1].(*Result)
	itemType := args[2].(string)

	err := db.AddToWantList(result.user.Username, data.id, datastructure.ItemType(itemType))
	result.result2 = 1

	return gott.Tuple(args), err
}

func removeFromWantlist(args ...interface{}) (interface{}, error) {
	data := args[0].(*RequestData)
	result := args[1].(*Result)
	itemType := args[2].(string)

	err := db.RemoveFromWantList(result.user.Username, data.id, datastructure.ItemType(itemType))

	return gott.Tuple(args), err
}

func getItem(args ...interface{}) (interface{}, error) {
	itemTypeName := args[2].(string)
	itemType := datastructure.ItemType(itemTypeName)
	var arg interface{}
	var err error
	switch itemType {
	case datastructure.ItemTypeFilm:
		arg, err = gott.
			NewResult(gott.Tuple(args)).
			Bind(getFilm).
			Bind(getCollection).
			Finish()
		if err == nil {
			args = arg.(gott.Tuple)
		}
	case datastructure.ItemTypeTvserie:
		arg, err = gott.
			NewResult(gott.Tuple(args)).
			Bind(getTvSerie).
			Finish()
		if err == nil {
			args = arg.(gott.Tuple)
		}
	case datastructure.ItemTypeBook:
		arg, err = gott.
			NewResult(gott.Tuple(args)).
			Bind(getBook).
			Bind(getDescription).
			Bind(getCover).
			Finish()
		if err == nil {
			args = arg.(gott.Tuple)
		}
	default:
		err = errors.New("Wrong ItemType: " + itemTypeName)
	}
	return gott.Tuple(args), err
}

func cacheItem(args ...interface{}) (interface{}, error) {
	data := args[0].(*RequestData)
	result := args[1].(*Result)
	refs := result.result2.(int)

	item := result.result.(datastructure.Item)

	itemInfo := item.GetItemInfo()

	err := db.SaveCacheItem(item.GetItemType(), data.id, itemInfo, refs)
	return gott.Tuple(args), err
}

func AddToWantlist(username string, auth accounts.Authentication, itemId, itemType, language, mimetype string) error {
	auth.Necessary = true
	_, err := gott.
		NewResult(gott.Tuple{&RequestData{id: itemId, language: language, mimetype: mimetype, auth: auth, username: username}, &Result{}, itemType}).
		Bind(parseLanguage).
		Bind(verifyToken).
		Bind(verifyUser).
		Bind(getItem).
		Bind(addToWantlist).
		Bind(cacheItem).
		Finish()

	return err
}

func tmdbNotFound(args ...interface{}) (interface{}, error) {
	data := args[0].(*RequestData)
	result := args[1].(*Result)
	err := args[len(args)-1].(error)
	if _, ok := err.(tmdb.NotFountError); ok {
		err = nil
		result.page = "/users/" + data.username + "/watchlist"
	}
	return gott.Tuple(args), err
}

func RemoveFromWantlist(username string, auth accounts.Authentication, itemId, itemType, language, mimetype string) (string, error) {
	auth.Necessary = true
	r, err := gott.
		NewResult(gott.Tuple{&RequestData{id: itemId, language: language, mimetype: mimetype, auth: auth, username: username}, &Result{}, itemType}).
		Bind(parseLanguage).
		Bind(verifyToken).
		Bind(verifyUser).
		Bind(getItem).
		Recover(tmdbNotFound).
		Bind(removeFromWantlist).
		Bind(removeCacheItem). //check
		Finish()

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

func splitItemId(args ...interface{}) interface{} {
	data := args[0].(*RequestData)
	itemId := args[3].(string)
	id := strings.Split(itemId, "/")
	data.id = id[0]
	return gott.Tuple(args)
}

func parseExperienceDate(args ...interface{}) (interface{}, error) {
	result := args[1].(*Result)
	datetime := args[4].(string)
	var t time.Time
	var err error = nil
	if datetime == "" {
		t = time.Now()
	} else {
		var location *time.Location
		if datetime != "0001-01-01T00:00:00" {
			location, _ = time.LoadLocation(result.user.Timezone)
		} else {
			location = time.UTC
		}
		t, err = time.ParseInLocation("2006-01-02T15:04:05", datetime, location)
	}
	t = t.In(time.UTC)
	result.result2 = t
	return gott.Tuple(args), err
}

func getSpecials(args ...interface{}) (interface{}, error) {
	itemId := args[3].(string)
	var err error

	id := strings.Split(itemId, "/")
	if len(id) > 1 && id[1][3] == 'A' {
		arg, err := gott.
			NewResult(gott.Tuple(args)).
			Bind(getSeason).
			Finish()
		if err == nil {
			args = arg.(gott.Tuple)
		}
	}
	return gott.Tuple(args), err
}

func addToExperiences(args ...interface{}) (interface{}, error) {
	data := args[0].(*RequestData)
	result := args[1].(*Result)
	t := result.result2.(time.Time)
	itemType := args[2].(string)
	itemId := args[3].(string)
	var (
		err  error = nil
		refs int
	)

	id := strings.Split(itemId, "/")
	if len(id) > 1 && id[1][3] == 'A' {
		serie := result.result.(*tmdb.TvSerie)
		var season int
		fmt.Sscanf(id[1][1:3], "%d", &season)
		episodes := []string{}
		for _, episode := range serie.Seasons[season].Episodes {
			episodes = append(episodes, data.id+"/"+episode.Episode_code)
		}
		refs, err = db.WatchWholeSerie(result.user.Username, id[0], episodes, datastructure.ItemType(itemType), t)
	} else {
		refs, err = db.AddToExperiences(result.user.Username, itemId, datastructure.ItemType(itemType), t)
	}
	result.result2 = refs

	if len(id) > 1 {
		return gott.Tuple(args), errors.New("Skip")
	}

	return gott.Tuple(args), err
}

func clearSpecials(args ...interface{}) (interface{}, error) {
	data := args[0].(*RequestData)
	result := args[1].(*Result)
	itemType := args[2].(string)
	itemId := args[3].(string)
	id := strings.Split(itemId, "/")

	var err error
	if len(id) > 1 && id[1][3] == 'A' {
		serie := result.result.(*tmdb.TvSerie)
		var season int
		fmt.Sscanf(id[1][1:3], "%d", &season)
		if season == 0 {
			episodes := []string{}
			for _, episode := range serie.Seasons[0].Episodes {
				episodes = append(episodes, data.id+"/"+episode.Episode_code)
			}
			err = db.ClearSpecials(result.user.Username, id[0], episodes, datastructure.ItemType(itemType))
		}
	}
	return gott.Tuple(args), err
}

func removeFromWantList(args ...interface{}) (interface{}, error) {
	data := args[0].(*RequestData)
	result := args[1].(*Result)
	itemType := args[2].(string)

	err := db.RemoveFromWantList(result.user.Username, data.id, datastructure.ItemType(itemType))

	return gott.Tuple(args), err
}

func removeCacheItem(args ...interface{}) (interface{}, error) {
	data := args[0].(*RequestData)
	itemType := args[2].(string)

	err := db.RemoveCacheItem(datastructure.ItemType(itemType), data.id)

	return gott.Tuple(args), err
}

func AddToExperiences(username string, auth accounts.Authentication, itemId, itemType, datetime, language, mimetype string) error {
	auth.Necessary = true
	_, err := gott.
		NewResult(gott.Tuple{&RequestData{language: language, mimetype: mimetype, auth: auth, username: username}, &Result{}, itemType, itemId, datetime}).
		Map(splitItemId).
		Bind(parseLanguage).
		Bind(verifyToken).
		Bind(verifyUser).
		Bind(parseExperienceDate).
		Bind(getItem).
		Bind(getSpecials).
		Bind(clearSpecials).
		Bind(addToExperiences).
		Bind(cacheItem).
		Bind(removeFromWantList).
		// todo remove from calendar
		Bind(removeCacheItem).
		Finish()

	if err != nil {
		if err.Error() == "Skip" {
			err = nil
		}

		if emptyErr, ok := err.(db.EmptyError); ok && emptyErr.Error() == "Empty delete" {
			err = nil
		}
	}

	return err
}

func removeSession(args ...interface{}) (interface{}, error) {
	data := args[0].(*RequestData)
	var token string
	if data.id == "0" {
		token = data.auth.Token
	} else {
		token = data.id
	}
	username := data.username
	err := db.RemoveSession(username, token)
	return gott.Tuple(args), err
}

func SessionDelete(username string, auth accounts.Authentication, session, languages, mimetype string) error {
	auth.Necessary = true
	_, err := gott.
		NewResult(gott.Tuple{&RequestData{id: session, language: languages, mimetype: mimetype, auth: auth, username: username}, &Result{}}).
		Bind(parseLanguage).
		Bind(verifyToken).
		Bind(verifyUser).
		Bind(removeSession).
		Finish()

	return err
}
