package models

import (
	"fmt"
	"time"

	"github.com/mayswind/ezbookkeeping/pkg/core"
	"github.com/mayswind/ezbookkeeping/pkg/utils"
)

// TransactionEditScope represents the scope which transaction can be edited
type TransactionEditScope byte

// Editable Transaction Ranges
const (
	TRANSACTION_EDIT_SCOPE_NONE                          TransactionEditScope = 0
	TRANSACTION_EDIT_SCOPE_ALL                           TransactionEditScope = 1
	TRANSACTION_EDIT_SCOPE_TODAY_OR_LATER                TransactionEditScope = 2
	TRANSACTION_EDIT_SCOPE_LAST_24H_OR_LATER             TransactionEditScope = 3
	TRANSACTION_EDIT_SCOPE_THIS_WEEK_OR_LATER            TransactionEditScope = 4
	TRANSACTION_EDIT_SCOPE_THIS_MONTH_OR_LATER           TransactionEditScope = 5
	TRANSACTION_EDIT_SCOPE_THIS_YEAR_OR_LATER            TransactionEditScope = 6
	TRANSACTION_EDIT_SCOPE_LAST_RECONCILED_TIME_OR_LATER TransactionEditScope = 7
	TRANSACTION_EDIT_SCOPE_INVALID                       TransactionEditScope = 255
)

// String returns a textual representation of the editable transaction ranges enum
func (s TransactionEditScope) String() string {
	switch s {
	case TRANSACTION_EDIT_SCOPE_NONE:
		return "None"
	case TRANSACTION_EDIT_SCOPE_ALL:
		return "All"
	case TRANSACTION_EDIT_SCOPE_TODAY_OR_LATER:
		return "TodayOrLater"
	case TRANSACTION_EDIT_SCOPE_LAST_24H_OR_LATER:
		return "Last24HourOrLater"
	case TRANSACTION_EDIT_SCOPE_THIS_WEEK_OR_LATER:
		return "ThisWeekOrLater"
	case TRANSACTION_EDIT_SCOPE_THIS_MONTH_OR_LATER:
		return "ThisMonthOrLater"
	case TRANSACTION_EDIT_SCOPE_THIS_YEAR_OR_LATER:
		return "ThisYearOrLater"
	case TRANSACTION_EDIT_SCOPE_LAST_RECONCILED_TIME_OR_LATER:
		return "LastReconciledTimeOrLater"
	case TRANSACTION_EDIT_SCOPE_INVALID:
		return "Invalid"
	default:
		return fmt.Sprintf("Invalid(%d)", int(s))
	}
}

// AmountColorType represents the type of amount color in frontend
type AmountColorType byte

// Amount Color Types
const (
	AMOUNT_COLOR_TYPE_DEFAULT        AmountColorType = 0
	AMOUNT_COLOR_TYPE_GREEN          AmountColorType = 1
	AMOUNT_COLOR_TYPE_RED            AmountColorType = 2
	AMOUNT_COLOR_TYPE_YELLOW         AmountColorType = 3
	AMOUNT_COLOR_TYPE_BLACK_OR_WHITE AmountColorType = 4
	AMOUNT_COLOR_TYPE_INVALID        AmountColorType = 255
)

// String returns a textual representation of the amount color type enum
func (s AmountColorType) String() string {
	switch s {
	case AMOUNT_COLOR_TYPE_DEFAULT:
		return "Default"
	case AMOUNT_COLOR_TYPE_GREEN:
		return "Green"
	case AMOUNT_COLOR_TYPE_RED:
		return "Red"
	case AMOUNT_COLOR_TYPE_YELLOW:
		return "Yellow"
	case AMOUNT_COLOR_TYPE_BLACK_OR_WHITE:
		return "Black or White"
	case AMOUNT_COLOR_TYPE_INVALID:
		return "Invalid"
	default:
		return fmt.Sprintf("Invalid(%d)", int(s))
	}
}

// User represents user data stored in database
type User struct {
	Uid                   int64  `xorm:"PK"`
	Username              string `xorm:"VARCHAR(32) UNIQUE NOT NULL"`
	Email                 string `xorm:"VARCHAR(100) UNIQUE NOT NULL"`
	Nickname              string `xorm:"VARCHAR(64) NOT NULL"`
	Password              string `xorm:"VARCHAR(64) NOT NULL"`
	Salt                  string `xorm:"VARCHAR(10) NOT NULL"`
	CustomAvatarType      string `xorm:"VARCHAR(10)"`
	DefaultAccountId      int64
	UseLastReconciledTime bool
	TransactionEditScope  TransactionEditScope       `xorm:"TINYINT NOT NULL"`
	Language              string                     `xorm:"VARCHAR(10)"`
	DefaultCurrency       string                     `xorm:"VARCHAR(3) NOT NULL"`
	FirstDayOfWeek        core.WeekDay               `xorm:"TINYINT NOT NULL"`
	FiscalYearStart       core.FiscalYearStart       `xorm:"SMALLINT"`
	CalendarDisplayType   core.CalendarDisplayType   `xorm:"TINYINT"`
	DateDisplayType       core.DateDisplayType       `xorm:"TINYINT"`
	LongDateFormat        core.LongDateFormat        `xorm:"TINYINT"`
	ShortDateFormat       core.ShortDateFormat       `xorm:"TINYINT"`
	LongTimeFormat        core.LongTimeFormat        `xorm:"TINYINT"`
	ShortTimeFormat       core.ShortTimeFormat       `xorm:"TINYINT"`
	FiscalYearFormat      core.FiscalYearFormat      `xorm:"TINYINT"`
	CurrencyDisplayType   core.CurrencyDisplayType   `xorm:"TINYINT"`
	NumeralSystem         core.NumeralSystem         `xorm:"TINYINT"`
	DecimalSeparator      core.DecimalSeparator      `xorm:"TINYINT"`
	DigitGroupingSymbol   core.DigitGroupingSymbol   `xorm:"TINYINT"`
	DigitGrouping         core.DigitGroupingType     `xorm:"TINYINT"`
	CoordinateDisplayType core.CoordinateDisplayType `xorm:"TINYINT"`
	ExpenseAmountColor    AmountColorType            `xorm:"TINYINT"`
	IncomeAmountColor     AmountColorType            `xorm:"TINYINT"`
	FeatureRestriction    core.UserFeatureRestrictions
	Disabled              bool
	Deleted               bool `xorm:"NOT NULL"`
	EmailVerified         bool `xorm:"NOT NULL"`
	CreatedUnixTime       int64
	UpdatedUnixTime       int64
	DeletedUnixTime       int64
	LastLoginUnixTime     int64
}

// UserBasicInfo represents a view-object of user basic info
type UserBasicInfo struct {
	Username              string                     `json:"username"`
	Email                 string                     `json:"email"`
	Nickname              string                     `json:"nickname"`
	AvatarUrl             string                     `json:"avatar"`
	AvatarProvider        string                     `json:"avatarProvider,omitempty"`
	DefaultAccountId      int64                      `json:"defaultAccountId,string"`
	UseLastReconciledTime bool                       `json:"useLastReconciledTime"`
	TransactionEditScope  TransactionEditScope       `json:"transactionEditScope"`
	Language              string                     `json:"language"`
	DefaultCurrency       string                     `json:"defaultCurrency"`
	FirstDayOfWeek        core.WeekDay               `json:"firstDayOfWeek"`
	FiscalYearStart       core.FiscalYearStart       `json:"fiscalYearStart"`
	CalendarDisplayType   core.CalendarDisplayType   `json:"calendarDisplayType"`
	DateDisplayType       core.DateDisplayType       `json:"dateDisplayType"`
	LongDateFormat        core.LongDateFormat        `json:"longDateFormat"`
	ShortDateFormat       core.ShortDateFormat       `json:"shortDateFormat"`
	LongTimeFormat        core.LongTimeFormat        `json:"longTimeFormat"`
	ShortTimeFormat       core.ShortTimeFormat       `json:"shortTimeFormat"`
	FiscalYearFormat      core.FiscalYearFormat      `json:"fiscalYearFormat"`
	CurrencyDisplayType   core.CurrencyDisplayType   `json:"currencyDisplayType"`
	NumeralSystem         core.NumeralSystem         `json:"numeralSystem"`
	DecimalSeparator      core.DecimalSeparator      `json:"decimalSeparator"`
	DigitGroupingSymbol   core.DigitGroupingSymbol   `json:"digitGroupingSymbol"`
	DigitGrouping         core.DigitGroupingType     `json:"digitGrouping"`
	CoordinateDisplayType core.CoordinateDisplayType `json:"coordinateDisplayType"`
	ExpenseAmountColor    AmountColorType            `json:"expenseAmountColor"`
	IncomeAmountColor     AmountColorType            `json:"incomeAmountColor"`
	EmailVerified         bool                       `json:"emailVerified"`
}

// UserLoginRequest represents all parameters of user login request
type UserLoginRequest struct {
	LoginName string `json:"loginName" binding:"required,notBlank,max=100,validUsername|validEmail"`
	Password  string `json:"password" binding:"required,min=6,max=128"`
}

// UserRegisterRequest represents all parameters of user registering request
type UserRegisterRequest struct {
	Username        string       `json:"username" binding:"required,notBlank,max=32,validUsername"`
	Email           string       `json:"email" binding:"required,notBlank,max=100,validEmail"`
	Nickname        string       `json:"nickname" binding:"required,notBlank,max=64,validNickname"`
	Password        string       `json:"password" binding:"required,min=6,max=128"`
	Language        string       `json:"language" binding:"required,min=2,max=16"`
	DefaultCurrency string       `json:"defaultCurrency" binding:"required,len=3,validCurrency"`
	FirstDayOfWeek  core.WeekDay `json:"firstDayOfWeek" binding:"min=0,max=6"`
	TransactionCategoryCreateBatchRequest
}

// UserVerifyEmailRequest represents all parameters of user verify email request
type UserVerifyEmailRequest struct {
	RequestNewToken bool `json:"requestNewToken" binding:"omitempty"`
}

// UserVerifyEmailResponse represents all response parameters after user have verified email
type UserVerifyEmailResponse struct {
	NewToken            string         `json:"newToken,omitempty"`
	User                *UserBasicInfo `json:"user"`
	NotificationContent string         `json:"notificationContent,omitempty"`
}

// UserResendVerifyEmailRequest represents all parameters of user resend verify email request
type UserResendVerifyEmailRequest struct {
	Email    string `json:"email" binding:"omitempty,max=100,validEmail"`
	Password string `json:"password" binding:"omitempty,min=6,max=128"`
}

// UserProfileUpdateRequest represents all parameters of user updating profile request
type UserProfileUpdateRequest struct {
	Email                 string                      `json:"email" binding:"omitempty,notBlank,max=100,validEmail"`
	Nickname              string                      `json:"nickname" binding:"omitempty,notBlank,max=64,validNickname"`
	Password              string                      `json:"password" binding:"omitempty,min=6,max=128"`
	OldPassword           string                      `json:"oldPassword" binding:"omitempty,min=6,max=128"`
	DefaultAccountId      int64                       `json:"defaultAccountId,string" binding:"omitempty,min=1"`
	UseLastReconciledTime *bool                       `json:"useLastReconciledTime" binding:"omitempty"`
	TransactionEditScope  *TransactionEditScope       `json:"transactionEditScope" binding:"omitempty,min=0,max=7"`
	Language              string                      `json:"language" binding:"omitempty,min=2,max=16"`
	DefaultCurrency       string                      `json:"defaultCurrency" binding:"omitempty,len=3,validCurrency"`
	FirstDayOfWeek        *core.WeekDay               `json:"firstDayOfWeek" binding:"omitempty,min=0,max=6"`
	FiscalYearStart       *core.FiscalYearStart       `json:"fiscalYearStart" binding:"omitempty,validFiscalYearStart"`
	CalendarDisplayType   *core.CalendarDisplayType   `json:"calendarDisplayType" binding:"omitempty,min=0,max=4"`
	DateDisplayType       *core.DateDisplayType       `json:"dateDisplayType" binding:"omitempty,min=0,max=3"`
	LongDateFormat        *core.LongDateFormat        `json:"longDateFormat" binding:"omitempty,min=0,max=3"`
	ShortDateFormat       *core.ShortDateFormat       `json:"shortDateFormat" binding:"omitempty,min=0,max=3"`
	LongTimeFormat        *core.LongTimeFormat        `json:"longTimeFormat" binding:"omitempty,min=0,max=3"`
	ShortTimeFormat       *core.ShortTimeFormat       `json:"shortTimeFormat" binding:"omitempty,min=0,max=3"`
	FiscalYearFormat      *core.FiscalYearFormat      `json:"fiscalYearFormat" binding:"omitempty,min=0,max=5"`
	CurrencyDisplayType   *core.CurrencyDisplayType   `json:"currencyDisplayType" binding:"omitempty,min=0,max=11"`
	NumeralSystem         *core.NumeralSystem         `json:"numeralSystem" binding:"omitempty,min=0,max=5"`
	DecimalSeparator      *core.DecimalSeparator      `json:"decimalSeparator" binding:"omitempty,min=0,max=3"`
	DigitGroupingSymbol   *core.DigitGroupingSymbol   `json:"digitGroupingSymbol" binding:"omitempty,min=0,max=4"`
	DigitGrouping         *core.DigitGroupingType     `json:"digitGrouping" binding:"omitempty,min=0,max=3"`
	CoordinateDisplayType *core.CoordinateDisplayType `json:"coordinateDisplayType" binding:"omitempty,min=0,max=6"`
	ExpenseAmountColor    *AmountColorType            `json:"expenseAmountColor" binding:"omitempty,min=0,max=4"`
	IncomeAmountColor     *AmountColorType            `json:"incomeAmountColor" binding:"omitempty,min=0,max=4"`
}

// UserProfileUpdateResponse represents the data returns to frontend after updating profile
type UserProfileUpdateResponse struct {
	User     *UserBasicInfo `json:"user"`
	NewToken string         `json:"newToken,omitempty"`
}

// UserProfileResponse represents a view-object of user profile
type UserProfileResponse struct {
	*UserBasicInfo
	NoPassword  bool  `json:"noPassword,omitempty"`
	LastLoginAt int64 `json:"lastLoginAt"`
}

// CanEditTransactionByTransactionTime returns whether this user can edit transaction with specified transaction time
func (u *User) CanEditTransactionByTransactionTime(transactionTime int64, clientTimezone *time.Location, account *Account, destinationAccount *Account) bool {
	if u.TransactionEditScope == TRANSACTION_EDIT_SCOPE_NONE {
		return false
	} else if u.TransactionEditScope == TRANSACTION_EDIT_SCOPE_ALL {
		return true
	}

	now := time.Now()

	transactionUnixTime := utils.GetUnixTimeFromTransactionTime(transactionTime)

	if u.TransactionEditScope == TRANSACTION_EDIT_SCOPE_LAST_24H_OR_LATER {
		return transactionUnixTime > now.Add(-24*time.Hour).Unix()
	}

	clientNow := now.In(clientTimezone)
	clientTodayStartTime := utils.GetStartOfDay(clientNow)

	if u.TransactionEditScope == TRANSACTION_EDIT_SCOPE_TODAY_OR_LATER {
		return transactionUnixTime > clientTodayStartTime.Unix()
	} else if u.TransactionEditScope == TRANSACTION_EDIT_SCOPE_THIS_WEEK_OR_LATER {
		dayOfWeek := int(now.Weekday()) - int(u.FirstDayOfWeek)

		if dayOfWeek < 0 {
			dayOfWeek += 7
		}

		clientWeekStartTime := clientTodayStartTime.AddDate(0, 0, -dayOfWeek)
		return transactionUnixTime > clientWeekStartTime.Unix()
	} else if u.TransactionEditScope == TRANSACTION_EDIT_SCOPE_THIS_MONTH_OR_LATER {
		clientMonthStartTime := clientTodayStartTime.AddDate(0, 0, -(now.Day() - 1))
		return transactionUnixTime > clientMonthStartTime.Unix()
	} else if u.TransactionEditScope == TRANSACTION_EDIT_SCOPE_THIS_YEAR_OR_LATER {
		clientYearStartTime := clientTodayStartTime.AddDate(0, 0, -(now.YearDay() - 1))
		return transactionUnixTime > clientYearStartTime.Unix()
	} else if u.TransactionEditScope == TRANSACTION_EDIT_SCOPE_LAST_RECONCILED_TIME_OR_LATER && u.UseLastReconciledTime {
		minAccountLastReconciledTime := int64(0)

		if account != nil {
			minAccountLastReconciledTime = account.GetLastReconciledTime()
		}

		if destinationAccount != nil {
			destinationAccountLastReconciledTime := destinationAccount.GetLastReconciledTime()

			if destinationAccountLastReconciledTime > minAccountLastReconciledTime {
				minAccountLastReconciledTime = destinationAccountLastReconciledTime
			}
		}

		return transactionUnixTime > minAccountLastReconciledTime
	}

	return false
}

// ToUserBasicInfo returns a user basic view-object according to database model
func (u *User) ToUserBasicInfo(avatarProvider core.UserAvatarProviderType, avatarUrl string) *UserBasicInfo {
	fiscalYearStart := u.FiscalYearStart

	if fiscalYearStart < core.FISCAL_YEAR_START_MIN || fiscalYearStart > core.FISCAL_YEAR_START_MAX {
		fiscalYearStart = core.FISCAL_YEAR_START_DEFAULT
	}

	return &UserBasicInfo{
		Username:              u.Username,
		Email:                 u.Email,
		Nickname:              u.Nickname,
		AvatarUrl:             avatarUrl,
		AvatarProvider:        string(avatarProvider),
		DefaultAccountId:      u.DefaultAccountId,
		UseLastReconciledTime: u.UseLastReconciledTime,
		TransactionEditScope:  u.TransactionEditScope,
		Language:              u.Language,
		DefaultCurrency:       u.DefaultCurrency,
		FirstDayOfWeek:        u.FirstDayOfWeek,
		FiscalYearStart:       fiscalYearStart,
		CalendarDisplayType:   u.CalendarDisplayType,
		DateDisplayType:       u.DateDisplayType,
		LongDateFormat:        u.LongDateFormat,
		ShortDateFormat:       u.ShortDateFormat,
		LongTimeFormat:        u.LongTimeFormat,
		ShortTimeFormat:       u.ShortTimeFormat,
		DecimalSeparator:      u.DecimalSeparator,
		FiscalYearFormat:      u.FiscalYearFormat,
		CurrencyDisplayType:   u.CurrencyDisplayType,
		NumeralSystem:         u.NumeralSystem,
		DigitGroupingSymbol:   u.DigitGroupingSymbol,
		DigitGrouping:         u.DigitGrouping,
		CoordinateDisplayType: u.CoordinateDisplayType,
		ExpenseAmountColor:    u.ExpenseAmountColor,
		IncomeAmountColor:     u.IncomeAmountColor,
		EmailVerified:         u.EmailVerified,
	}
}

// ToUserProfileResponse returns a user profile view-object according to database model
func (u *User) ToUserProfileResponse(basicInfo *UserBasicInfo) *UserProfileResponse {
	return &UserProfileResponse{
		UserBasicInfo: basicInfo,
		NoPassword:    u.Password == "",
		LastLoginAt:   u.LastLoginUnixTime,
	}
}
