package arn

import (
	"errors"
	"fmt"
	"reflect"
	"strings"

	"github.com/animenotifier/notify.moe/arn/validate"

	"github.com/aerogo/aero"
	"github.com/aerogo/api"
	"github.com/aerogo/http/client"
	"github.com/akyoto/color"
	"github.com/animenotifier/notify.moe/arn/autocorrect"
)

// Force interface implementations
var (
	_ PostParent   = (*User)(nil)
	_ api.Editable = (*User)(nil)
)

// Authorize returns an error if the given API POST request is not authorized.
func (user *User) Authorize(ctx aero.Context, action string) error {
	editor := GetUserFromContext(ctx)

	if editor == nil {
		return errors.New("Not authorized")
	}

	if editor.ID != ctx.Get("id") && editor.Role != "admin" {
		return errors.New("Can not modify data from other users")
	}

	return nil
}

// Edit updates the user object.
func (user *User) Edit(ctx aero.Context, key string, value reflect.Value, newValue reflect.Value) (bool, error) {
	switch key {
	case "Nick":
		newNick := newValue.String()
		err := user.SetNick(newNick)
		return true, err

	case "Email":
		newEmail := newValue.String()
		err := user.SetEmail(newEmail)
		return true, err

	case "Gender":
		newGender := newValue.String()

		if newGender != "male" && newGender != "female" {
			return true, errors.New("Invalid gender")
		}

		user.Gender = newGender
		return true, nil

	case "Website":
		newSite := newValue.String()

		if newSite == "" {
			user.Website = newSite
			return true, nil
		}

		if autocorrect.IsTrackerLink(newSite) {
			return true, errors.New("Not an actual personal website or homepage")
		}

		newSite = autocorrect.Website(newSite)

		if !validate.URI("https://" + newSite) {
			return true, errors.New("Not a valid website link")
		}

		response, err := client.Get("https://" + newSite).End()

		if err != nil || response.StatusCode() >= 400 {
			return true, fmt.Errorf("https://%s seems to be inaccessible", newSite)
		}

		user.Website = newSite
		return true, nil

	case "BirthDay":
		newBirthDay := newValue.String()

		if AgeInYears(newBirthDay) <= 0 {
			return true, errors.New("Invalid birthday (make sure to use YYYY-MM-DD format, e.g. 2000-01-17)")
		}

		user.BirthDay = newBirthDay
		return true, nil

	case "ProExpires", "Avatar.Extension", "Avatar.LastModified":
		editor := GetUserFromContext(ctx)

		if editor == nil || editor.Role != "admin" {
			return true, errors.New("Not authorized to edit")
		}

	case "Accounts.Discord.Nick":
		newNick := newValue.String()

		if newNick == "" {
			value.SetString(newNick)
			user.Accounts.Discord.Verified = false
			return true, nil
		}

		if !validate.DiscordNick(newNick) {
			return true, errors.New("Discord username must include your name and the 4-digit Discord tag (e.g. Yandere#1234)")
		}

		// Trim spaces
		parts := strings.Split(newNick, "#")
		parts[0] = strings.TrimSpace(parts[0])
		parts[1] = strings.TrimSpace(parts[1])
		newNick = strings.Join(parts, "#")

		if value.String() != newNick {
			value.SetString(newNick)
			user.Accounts.Discord.Verified = false
		}

		return true, nil

	case "Accounts.Overwatch.BattleTag":
		newBattleTag := newValue.String()
		value.SetString(newBattleTag)

		if newBattleTag == "" {
			user.Accounts.Overwatch.SkillRating = 0
			user.Accounts.Overwatch.Tier = ""
		} else {
			// Refresh Overwatch info if the battletag changed
			go func() {
				err := user.RefreshOverwatchInfo()

				if err != nil {
					color.Red("Error refreshing Overwatch info of user '%s' with Overwatch battle tag '%s': %v", user.Nick, newBattleTag, err)
					return
				}

				color.Green("Refreshed Overwatch info of user '%s' with Overwatch battle tag '%s': %v", user.Nick, newBattleTag, user.Accounts.Overwatch.SkillRating)
				user.Save()
			}()
		}

		return true, nil

	case "Accounts.FinalFantasyXIV.Nick", "Accounts.FinalFantasyXIV.Server":
		newValue := newValue.String()
		value.SetString(newValue)

		if newValue == "" {
			user.Accounts.FinalFantasyXIV.Class = ""
			user.Accounts.FinalFantasyXIV.Level = 0
			user.Accounts.FinalFantasyXIV.ItemLevel = 0
		} else if user.Accounts.FinalFantasyXIV.Nick != "" && user.Accounts.FinalFantasyXIV.Server != "" {
			// Refresh FinalFantasyXIV info if the name or server changed
			go func() {
				err := user.RefreshFFXIVInfo()

				if err != nil {
					color.Red("Error refreshing FinalFantasy XIV info of user '%s' with nick '%s' on server '%s': %v", user.Nick, user.Accounts.FinalFantasyXIV.Nick, user.Accounts.FinalFantasyXIV.Server, err)
					return
				}

				user.Save()
			}()
		}

		return true, nil
	}

	// Automatically correct account nicks
	if strings.HasPrefix(key, "Accounts.") && strings.HasSuffix(key, ".Nick") {
		newNick := newValue.String()
		newNick = autocorrect.AccountNick(newNick)
		value.SetString(newNick)

		// Refresh osu info if the name changed
		if key == "Accounts.Osu.Nick" {
			if newNick == "" {
				user.Accounts.Osu.PP = 0
				user.Accounts.Osu.Level = 0
				user.Accounts.Osu.Accuracy = 0
			} else {
				go func() {
					err := user.RefreshOsuInfo()

					if err != nil {
						color.Red("Error refreshing osu info of user '%s' with osu nick '%s': %v", user.Nick, newNick, err)
						return
					}

					color.Green("Refreshed osu info of user '%s' with osu nick '%s': %v", user.Nick, newNick, user.Accounts.Osu.PP)
					user.Save()
				}()
			}
		}

		return true, nil
	}

	return false, nil
}

// Save saves the user object in the database.
func (user *User) Save() {
	DB.Set("User", user.ID, user)
}

// Filter removes privacy critical fields from the user object.
func (user *User) Filter() {
	user.Email = ""
	user.Gender = ""
	user.FirstName = ""
	user.LastName = ""
	user.IP = ""
	user.UserAgent = ""
	user.LastLogin = ""
	user.Accounts.Facebook.ID = ""
	user.Accounts.Google.ID = ""
	user.Accounts.Twitter.ID = ""
	user.BirthDay = ""
	user.Location = &Location{}
	user.Browser = UserBrowser{}
	user.OS = UserOS{}
}

// ShouldFilter tells whether data needs to be filtered in the given context.
func (user *User) ShouldFilter(ctx aero.Context) bool {
	ctxUser := GetUserFromContext(ctx)

	if ctxUser != nil && ctxUser.Role == "admin" {
		return false
	}

	return true
}