package models

import (
	"errors"
	"strconv"
	"time"

	"code.justin.tv/common/yimg"
)

type FilterParams struct {
	IDs          []string
	Logins       []string
	Emails       []string
	DisplayNames []string
	Ips          []string
	PhoneNumbers []string

	NotDeleted      bool
	NoTOSViolation  bool
	NoDMCAViolation bool

	// InlineIdentifierValidation returns users with valid identifiers as results and users with invalid identifiers in errors
	InlineIdentifierValidation bool
}

type ChannelFilterParams struct {
	NotDeleted      bool
	NoTOSViolation  bool
	NoDMCAViolation bool

	// InlineIdentifierValidation returns channels with valid identifiers as results and users with invalid identifiers in errors
	InlineIdentifierValidation bool
}

// FilterLength returns the combined length of all filters. This is used for optimized request batching.
func (f *FilterParams) FilterLength() int {
	return len(f.IDs) + len(f.Logins) + len(f.Emails) + len(f.DisplayNames) + len(f.Ips) + len(f.PhoneNumbers)
}

const (
	NotDeletedParam      = "not_deleted"
	NoTOSViolationParam  = "no_tos_violation"
	NoDMCAViolationParam = "no_dmca_violation"

	InlineIdentifierValidation = "inline_identifier_validation"

	LoginRenameCooldown = 60 // Number of days before you can rename login
	EmailChangeCooldown = 1  // Number of days before you can update email

	ReturnUploadInfoAsStruct = "return_upload_info_as_struct"

	ChannelOfflineImageType = "channel_offline_image"
	ProfileBannerImageType  = "profile_banner"
	ProfilePictureImageType = "profile_image"

	GameMaxLen = 255
)

type ImageFunc func(yimg.Image, string) (string, error)

type PropertiesResult struct {
	Results        []*Properties      `json:"results"`
	BadIdentifiers []ErrBadIdentifier `json:"bad_identifiers"`
}

type PropertiesResultExternal struct {
	Results        []*PropertiesExternal `json:"results"`
	BadIdentifiers []ErrBadIdentifier    `json:"bad_identifiers"`
}

type IDsResult struct {
	Results        []*IDOnlyProperties `json:"results"`
	BadIdentifiers []ErrBadIdentifier  `json:"bad_identifiers"`
}

// UsernameResetTokenResult contains the response from creating a Username Reset token
type UsernameResetTokenResult struct {
	UsernameResetToken string `json:"username_reset_token"`
}

func (pr PropertiesResult) ConvertToExternal() PropertiesResult {
	pre := PropertiesResult{}
	result := pr.Results
	if result == nil || len(result) == 0 {
		return pre
	}

	resultsExternal := []*Properties{}
	for _, r := range result {
		if r != nil {
			re := (*r).ConvertToExternal()
			resultsExternal = append(resultsExternal, &re)
		}
	}

	return PropertiesResult{Results: resultsExternal}
}

type Properties struct {
	ID                      string       `json:"id" `
	Login                   *string      `json:"login" `
	Birthday                *time.Time   `json:"birthday" `
	DmcaViolation           *bool        `json:"dmca_violation" `
	TermsOfServiceViolation *bool        `json:"terms_of_service_violation" `
	DeletedOn               *time.Time   `json:"deleted_on" `
	UsernameFlaggedOn       *time.Time   `json:"username_flagged_on" `
	Language                *string      `json:"language" `
	Category                *string      `json:"category" `
	RemoteIP                *string      `json:"remote_ip" `
	Location                *string      `json:"location" `
	Email                   *string      `json:"email" `
	LastLogin               *string      `json:"last_login" `
	BannedUntil             *time.Time   `json:"banned_until" `
	DmcaViolationCount      *int         `json:"dmca_violation_count" `
	TosViolationCount       *int         `json:"tos_violation_count" `
	Admin                   *bool        `json:"admin" `
	Subadmin                *bool        `json:"subadmin" `
	GlobalMod               *bool        `json:"global_mod" `
	Displayname             *string      `json:"displayname" `
	Description             *string      `json:"description" `
	ProfileImage            *yimg.Images `json:"profile_image" `
	ProfileImageURL         *string      `json:"profile_image_url" `
	UpdatedOn               *time.Time   `json:"updated_on" `
	CreatedOn               *time.Time   `json:"created_on" `
	EmailVerified           *bool        `json:"email_verified" `
	PhoneNumber             *string      `json:"phone_number" `
	LastLoginChangeDate     *time.Time   `json:"last_login_change_date" `
	EnableEmailReuse        *bool        `json:"enable_email_reuse"`
	FlaggedToDelete         *bool        `json:"flagged_to_delete"`
	StoppedSMS              *bool        `json:"stopped_sms"`
	HideCreated             *bool        `json:"hide_created"`
	DeletionHold            *bool        `json:"deletion_hold"`
	PrimaryColorHex         *string      `json:"primary_color_hex"`
	LastEmailChangeDate     *time.Time   `json:"last_email_change_date"`
	EmailRevertSuccess      *bool        `json:"email_revert_success"`
}

type PropertiesExternal struct {
	ID                      string       `json:"id" `
	Login                   *string      `json:"login" `
	Language                *string      `json:"language" `
	Category                *string      `json:"category" `
	Admin                   *bool        `json:"admin" `
	Subadmin                *bool        `json:"subadmin" `
	GlobalMod               *bool        `json:"global_mod" `
	Displayname             *string      `json:"displayname" `
	Description             *string      `json:"description" `
	ProfileImage            *yimg.Images `json:"profile_image" `
	ProfileImageURL         *string      `json:"profile_image_url" `
	UpdatedOn               *time.Time   `json:"updated_on" `
	CreatedOn               *time.Time   `json:"created_on" `
	LastLoginChangeDate     *time.Time   `json:"last_login_change_date" `
	DeletedOn               *time.Time   `json:"deleted_on" `
	UsernameFlaggedOn       *time.Time   `json:"username_flagged_on" `
	DmcaViolationCount      *int         `json:"dmca_violation_count" `
	TosViolationCount       *int         `json:"tos_violation_count" `
	DmcaViolation           *bool        `json:"dmca_violation" `
	TermsOfServiceViolation *bool        `json:"terms_of_service_violation" `
}

func (p Properties) ConvertToExternal() Properties {
	return Properties{
		ID:                      p.ID,
		Login:                   p.Login,
		Language:                p.Language,
		Category:                p.Category,
		Admin:                   p.Admin,
		Subadmin:                p.Subadmin,
		GlobalMod:               p.GlobalMod,
		Displayname:             p.Displayname,
		Description:             p.Description,
		ProfileImage:            p.ProfileImage,
		ProfileImageURL:         p.ProfileImageURL,
		UpdatedOn:               p.UpdatedOn,
		CreatedOn:               p.CreatedOn,
		LastLoginChangeDate:     p.LastLoginChangeDate,
		DeletedOn:               p.DeletedOn,
		UsernameFlaggedOn:       p.UsernameFlaggedOn,
		DmcaViolationCount:      p.DmcaViolationCount,
		TosViolationCount:       p.TosViolationCount,
		DmcaViolation:           p.DmcaViolation,
		TermsOfServiceViolation: p.TermsOfServiceViolation,
	}
}

const (
	UserIDCacheKey          = "u.id"
	UserLoginCacheKey       = "login"
	UserDisplaynameCacheKey = "displayname"
)

func (p Properties) CachePairs() []CachePair {
	var pairs []CachePair

	if p.ID != "" {
		pairs = append(pairs, CachePair{
			Key:   UserIDCacheKey,
			Value: p.ID,
		})
	}
	if p.Login != nil && *p.Login != "" {
		pairs = append(pairs, CachePair{
			Key:   UserLoginCacheKey,
			Value: *p.Login,
		})
	}
	if p.Displayname != nil && *p.Displayname != "" {
		pairs = append(pairs, CachePair{
			Key:   UserDisplaynameCacheKey,
			Value: *p.Displayname,
		})
	}

	return pairs
}

func (p Properties) HasTosViolation() bool {
	return BoolValue(p.TermsOfServiceViolation)
}

func (p Properties) HasDmcaViolation() bool {
	return BoolValue(p.DmcaViolation)
}

func (p Properties) TosStrikeCount() int {
	if p.TosViolationCount != nil {
		return *p.TosViolationCount
	}
	return 0
}

func (p Properties) DmcaStrikeCount() int {
	if p.DmcaViolationCount != nil {
		return *p.DmcaViolationCount
	}
	return 0
}

func (p Properties) IsIndefinitelySuspended() bool {
	return ((p.HasDmcaViolation() && p.DmcaStrikeCount() >= 3) || (p.HasTosViolation() && p.TosStrikeCount() >= 3)) && p.BannedUntil == nil
}

type PropertiesIterator []Properties

func (i PropertiesIterator) Each(f func(Cacheable) error) error {
	for _, prop := range i {
		if err := f(prop); err != nil {
			return err
		}
	}

	return nil
}

func (p Properties) ConvertToIntIDProperties() (IntIDProperties, error) {
	i, err := strconv.ParseUint(p.ID, 10, 64)
	if err != nil {
		return IntIDProperties{}, errors.New("Unable to return string user id as int")
	}
	return IntIDProperties{
		ID:                      i,
		Login:                   p.Login,
		Birthday:                p.Birthday,
		DmcaViolation:           p.DmcaViolation,
		TermsOfServiceViolation: p.TermsOfServiceViolation,
		DeletedOn:               p.DeletedOn,
		UsernameFlaggedOn:       p.UsernameFlaggedOn,
		Language:                p.Language,
		Category:                p.Category,
		RemoteIP:                p.RemoteIP,
		Location:                p.Location,
		Email:                   p.Email,
		EnableEmailReuse:        p.EnableEmailReuse,
		LastLogin:               p.LastLogin,
		BannedUntil:             p.BannedUntil,
		DmcaViolationCount:      p.DmcaViolationCount,
		TosViolationCount:       p.TosViolationCount,
		Admin:                   p.Admin,
		Subadmin:                p.Subadmin,
		GlobalMod:               p.GlobalMod,
		Displayname:             p.Displayname,
		Description:             p.Description,
		ProfileImage:            p.ProfileImage,
		ProfileImageURL:         p.ProfileImageURL,
		UpdatedOn:               p.UpdatedOn,
		CreatedOn:               p.CreatedOn,
		EmailVerified:           p.EmailVerified,
		PhoneNumber:             p.PhoneNumber,
		LastLoginChangeDate:     p.LastLoginChangeDate,
		FlaggedToDelete:         p.FlaggedToDelete,
		StoppedSMS:              p.StoppedSMS,
		HideCreated:             p.HideCreated,
		DeletionHold:            p.DeletionHold,
		PrimaryColorHex:         p.PrimaryColorHex,
		LastEmailChangeDate:     p.LastEmailChangeDate,
		EmailRevertSuccess:      p.EmailRevertSuccess,
	}, nil
}

func (p Properties) ConvertToIDOnlyProperties() IDOnlyProperties {
	return IDOnlyProperties{
		ID: p.ID,
	}
}

type IntIDProperties struct {
	ID                      uint64       `json:"id" `
	Login                   *string      `json:"login" `
	Birthday                *time.Time   `json:"birthday" `
	DmcaViolation           *bool        `json:"dmca_violation" `
	TermsOfServiceViolation *bool        `json:"terms_of_service_violation" `
	DeletedOn               *time.Time   `json:"deleted_on" `
	UsernameFlaggedOn       *time.Time   `json:"username_flagged_on" `
	Language                *string      `json:"language" `
	Category                *string      `json:"category" `
	RemoteIP                *string      `json:"remote_ip" `
	Location                *string      `json:"location" `
	Email                   *string      `json:"email" `
	LastLogin               *string      `json:"last_login" `
	BannedUntil             *time.Time   `json:"banned_until" `
	DmcaViolationCount      *int         `json:"dmca_violation_count" `
	TosViolationCount       *int         `json:"tos_violation_count" `
	Admin                   *bool        `json:"admin" `
	Subadmin                *bool        `json:"subadmin" `
	GlobalMod               *bool        `json:"global_mod" `
	Displayname             *string      `json:"displayname" `
	Description             *string      `json:"description" `
	ProfileImage            *yimg.Images `json:"profile_image" `
	ProfileImageURL         *string      `json:"profile_image_url" `
	UpdatedOn               *time.Time   `json:"updated_on" `
	CreatedOn               *time.Time   `json:"created_on" `
	EmailVerified           *bool        `json:"email_verified" `
	PhoneNumber             *string      `json:"phone_number" `
	LastLoginChangeDate     *time.Time   `json:"last_login_change_date" `
	EnableEmailReuse        *bool        `json:"enable_email_reuse" `
	FlaggedToDelete         *bool        `json:"flagged_to_delete"`
	StoppedSMS              *bool        `json:"stopped_sms"`
	HideCreated             *bool        `json:"hide_created"`
	DeletionHold            *bool        `json:"deletion_hold"`
	PrimaryColorHex         *string      `json:"primary_color_hex"`
	LastEmailChangeDate     *time.Time   `json:"last_email_change_date"`
	EmailRevertSuccess      *bool        `json:"email_revert_success"`
}

type IDOnlyProperties struct {
	ID string `json:"id" `
}

type RenameProperties struct {
	RenameEligible   *bool      `json:"rename_eligible"`
	RenameEligibleOn *time.Time `json:"rename_eligible_on"`
}

type LoginTypePropertiesResult struct {
	Results []*LoginTypeProperties `json:"results"`
}

type LoginTypeProperties struct {
	ID    string `json:"id,omitempty" `
	Login string `json:"login" `
	Type  string `json:"type" `
}

type LoginProperties struct {
	ID    string  `json:"id" `
	Login *string `json:"login" `
}

type ImageProperties struct {
	ID                         string       `json:"id" `
	ProfileBanner              *yimg.Images `json:"profile_banner" `
	ChannelOfflineImage        *yimg.Images `json:"channel_offline_image" `
	ProfileImage               *yimg.Images `json:"profile_image" `
	DefaultProfileBanner       *string      `json:"default_profile_banner" `
	DefaultChannelOfflineImage *string      `json:"default_channel_offline_image" `
	DefaultProfileImage        *string      `json:"default_profile_image" `
}

type UploadableImage struct {
	ID     string `json:"-" `
	Editor string `json:"editor"`
	Type   string `json:"type" `
	Format string `json:"format"`
}

type UploadInfo struct {
	ID  string `json:"id"`
	URL string `json:"url"`
}

type UpdateableProperties struct {
	ID                      string     `json:"-" `
	Login                   *string    `json:"-" `
	Birthday                *time.Time `json:"birthday" `
	Email                   *string    `json:"email" `
	Displayname             *string    `json:"displayname" `
	Language                *string    `json:"language" `
	Description             *string    `json:"description"`
	EmailVerified           *bool      `json:"email_verified"`
	NewLogin                *string    `json:"new_login" compareTo:"Login"`
	SkipLoginCooldown       *bool      `json:"skip_login_cooldown"`
	OverrideLoginBlock      *bool      `json:"override_login_block"`
	OverrideLoginLength     bool       `json:"override_login_length"`
	OverrideDisplayname     bool       `json:"override_displayname"`
	OverrideEmailReuse      bool       `json:"override_email_reuse"`
	OverrideEmailCooldown   bool       `json:"override_email_cooldown"`
	LastLogin               *string    `json:"last_login"`
	IncludeVerificationCode bool       `json:"include_verification_code"`
	EnableEmailReuse        *bool      `json:"enable_email_reuse"`
	FlaggedToDelete         *bool      `json:"flagged_to_delete"`
	StoppedSMS              *bool      `json:"stopped_sms"`
	HideCreated             *bool      `json:"hide_created"`
	DeletionHold            *bool      `json:"deletion_hold"`
	SkipNameValidation      bool       `json:"skip_name_validation"`
	EmailRevertSuccess      *bool      `json:"email_revert_success"`
	SkipUserNotification    *bool      `json:"skip_user_notification"`

	PrimaryColorHex       *string `json:"primary_color_hex"`
	DeletePrimaryColorHex bool    `json:"delete_primary_color_hex,omitempty"`

	ReleaseDateDuration time.Duration `json:"-"`

	PhoneNumber       *string `json:"phone_number"`
	DeletePhoneNumber bool    `json:"delete_phone_number,omitempty"`

	RemoteIP *string `json:"remote_ip"`
	DeviceID *string `json:"device_id"`
	// Set using RemoteIP
	Location *string `json:"-"`

	// Set internally for username resets
	UsernameReset bool `json:"-"`

	// LDAP ID for admin performing action
	AdminLogin string `json:"admin_login"`
}

type UsernameResetProperties struct {
	Login string `json:"login"`
	Token string `json:"token"`

	// Admin only
	ID                   string `json:"id"`
	Admin                string `json:"admin"`
	OverrideLoginBlock   bool   `json:"override_login_block"`
	OverrideLoginLength  bool   `json:"override_login_length"`
	SkipNameValidation   bool   `json:"skip_name_validation"`
	SkipUserNotification bool   `json:"skip_user_notification"`
}

type UserRoleProperties struct {
	Admin     *bool `json:"admin" `
	Subadmin  *bool `json:"subadmin" `
	GlobalMod *bool `json:"global_mod" `
}

type CreateUserProperties struct {
	Login string `json:"login" validate:"nonzero"`
	IP    string `json:"ip"`
	// Location is set from IP
	Location                string   `json:"-"`
	Birthday                Birthday `json:"birthday" validate:"nonzero"`
	Email                   string   `json:"email"`
	PhoneNumber             string   `json:"phone_number"`
	DeviceID                string   `json:"device_id"`
	Language                string   `json:"language"`
	Category                string   `json:"category"`
	Displayname             string   `json:"-"`
	IncludeVerificationCode bool     `json:"include_verification_code"`
	SkipNameValidation      bool     `json:"skip_name_validation"`
}

// BanUserProperties are the parameters to ban a user
type BanUserProperties struct {
	FromUserID     string     `json:"reporter"`
	TargetUserID   string     `json:"target_user_id" validate:"nonzero"`
	Reason         string     `json:"reason"`
	DetailedReason string     `json:"detailed_reason" `
	Description    string     `json:"description" `
	Content        string     `json:"content" `
	SkipIPBan      bool       `json:"skip_ip_ban" `
	ClearImages    bool       `json:"clear_images" `
	Type           string     `json:"type" validate:"regexp=tos|dmca"`
	Permanent      bool       `json:"is_permanent" `
	Time           *time.Time `json:"time" `
	Count          int        `json:"count" `
	Origin         string     `json:"origin" `
}

// UnbanUserProperties are optional parameters to
// change user violation counts for user unbans
type UnbanUserProperties struct {
	ResetCount    bool
	DecrementTos  bool
	DecrementDmca bool
	Decrement     int
	FromUserID    string
}

type Birthday struct {
	Day   int        `json:"day" validate:"nonzero"`
	Month time.Month `json:"month" validate:"nonzero"`
	Year  int        `json:"year" validate:"nonzero"`
}

func (b *Birthday) ToDate() time.Time {
	return time.Date(b.Year, b.Month, b.Day, 0, 0, 0, 0, time.Local)
}

func (u *UpdateableProperties) FillFromProperties(p *Properties) {
	u.ID = p.ID
	u.Login = p.Login

	if u.Email == nil {
		u.Email = p.Email
	}
	if u.Displayname == nil {
		u.Displayname = p.Displayname
	}
	if u.Language == nil {
		u.Language = p.Language
	}
	if u.Description == nil {
		u.Description = p.Description
	}
	if u.EmailVerified == nil {
		u.EmailVerified = p.EmailVerified
	}
	if u.LastLogin == nil {
		u.LastLogin = p.LastLogin
	}
	if u.PhoneNumber == nil && !u.DeletePhoneNumber {
		u.PhoneNumber = p.PhoneNumber
	}

	if u.RemoteIP == nil {
		u.RemoteIP = p.RemoteIP
		u.Location = p.Location
	}
	if u.Birthday == nil {
		u.Birthday = p.Birthday
	}
	if u.FlaggedToDelete == nil {
		u.FlaggedToDelete = p.FlaggedToDelete
	}
	if u.StoppedSMS == nil {
		u.StoppedSMS = p.StoppedSMS
	}
	if u.HideCreated == nil {
		u.HideCreated = p.HideCreated
	}
	if u.DeletionHold == nil {
		u.DeletionHold = p.DeletionHold
	}
	if u.PrimaryColorHex == nil && !u.DeletePrimaryColorHex {
		u.PrimaryColorHex = p.PrimaryColorHex
	}
}

func (u *UpdateableProperties) IsUserPropertiesUpdate(p *Properties) bool {
	return !isEqualString(u.Email, p.Email) ||
		!isEqualString(u.Displayname, p.Displayname) ||
		!isEqualString(u.Description, p.Description) ||
		!isEqualString(u.Language, p.Language) ||
		!isEqualString(u.LastLogin, p.LastLogin) ||
		!isEqualString(u.RemoteIP, p.RemoteIP) ||
		!isEqualString(u.Location, p.Location) ||
		!isEqualTime(u.Birthday, p.Birthday) ||
		!isEqualString(u.PhoneNumber, p.PhoneNumber) ||
		!isEqualBool(u.FlaggedToDelete, p.FlaggedToDelete) ||
		!isEqualBool(u.StoppedSMS, p.StoppedSMS) ||
		!isEqualBool(u.HideCreated, p.HideCreated) ||
		!isEqualBool(u.DeletionHold, p.DeletionHold) ||
		!isEqualString(u.PrimaryColorHex, p.PrimaryColorHex) ||
		!isEqualBool(u.EmailVerified, p.EmailVerified)
}

// FillFromProperties will set the existing admin, subadmin, and global_mod
// values from Properties if the respective UserRoleProperties values are nil,
// defaulting to false if both values are nil.
func (u *UserRoleProperties) FillFromProperties(p *Properties) {
	if u.Admin == nil {
		admin := false
		if p.Admin != nil {
			admin = *p.Admin
		}
		u.Admin = &admin
	}
	if u.Subadmin == nil {
		subadmin := false
		if p.Subadmin != nil {
			subadmin = *p.Subadmin
		}
		u.Subadmin = &subadmin
	}
	if u.GlobalMod == nil {
		globalMod := false
		if p.GlobalMod != nil {
			globalMod = *p.GlobalMod
		}
		u.GlobalMod = &globalMod
	}
}

// HardDeleteUserOptions are optional properties that can be passed when
// hard deleting a user.
type HardDeleteUserOptions struct {
	SkipBlock  bool
	AdminLogin string
}

type UserPendingDestroy struct {
	ID        string
	Login     string
	CreatedOn *time.Time
}
