package storage

import (
	"database/sql/driver"
	"encoding/json"
	"fmt"

	"sproutgate-backend/internal/models"
)

// ─── JSON 序列化辅助类型 ────────────────────────────────────────────────────────

// StringSlice 将 []string 序列化为 JSON 字符串存入 TEXT 列。
type StringSlice []string

func (s StringSlice) Value() (driver.Value, error) {
	if s == nil {
		return "[]", nil
	}
	b, err := json.Marshal([]string(s))
	return string(b), err
}

func (s *StringSlice) Scan(value interface{}) error {
	if value == nil {
		*s = StringSlice{}
		return nil
	}
	var raw []byte
	switch v := value.(type) {
	case []byte:
		raw = v
	case string:
		raw = []byte(v)
	default:
		return fmt.Errorf("StringSlice.Scan: unsupported type %T", value)
	}
	if len(raw) == 0 || string(raw) == "null" {
		*s = StringSlice{}
		return nil
	}
	return json.Unmarshal(raw, (*[]string)(s))
}

// AuthClientSlice 将 []models.AuthClientEntry 序列化为 JSON 存入 TEXT 列。
type AuthClientSlice []models.AuthClientEntry

func (a AuthClientSlice) Value() (driver.Value, error) {
	if a == nil {
		return "[]", nil
	}
	b, err := json.Marshal([]models.AuthClientEntry(a))
	return string(b), err
}

func (a *AuthClientSlice) Scan(value interface{}) error {
	if value == nil {
		*a = AuthClientSlice{}
		return nil
	}
	var raw []byte
	switch v := value.(type) {
	case []byte:
		raw = v
	case string:
		raw = []byte(v)
	default:
		return fmt.Errorf("AuthClientSlice.Scan: unsupported type %T", value)
	}
	if len(raw) == 0 || string(raw) == "null" {
		*a = AuthClientSlice{}
		return nil
	}
	return json.Unmarshal(raw, (*[]models.AuthClientEntry)(a))
}

// ─── GORM DB 模型 ─────────────────────────────────────────────────────────────

// DBUser 对应数据库 users 表。
type DBUser struct {
	Account                  string          `gorm:"primaryKey;column:account;size:255"`
	PasswordHash             string          `gorm:"column:password_hash;not null"`
	Username                 string          `gorm:"column:username;not null;size:255"`
	Email                    string          `gorm:"column:email;not null;size:255;index"`
	Level                    int             `gorm:"column:level;default:0"`
	SproutCoins              int             `gorm:"column:sprout_coins;default:0"`
	LastCheckInDate          string          `gorm:"column:last_check_in_date;size:20"`
	LastCheckInAt            string          `gorm:"column:last_check_in_at;size:128"`
	LastVisitDate            string          `gorm:"column:last_visit_date;size:20"`
	LastVisitAt              string          `gorm:"column:last_visit_at;size:128"`
	LastVisitIP              string          `gorm:"column:last_visit_ip;size:45"`
	LastVisitDisplayLocation string          `gorm:"column:last_visit_display_location;size:512"`
	CheckInTimes             StringSlice     `gorm:"column:check_in_times;type:mediumtext"`
	VisitTimes               StringSlice     `gorm:"column:visit_times;type:mediumtext"`
	SecondaryEmails          StringSlice     `gorm:"column:secondary_emails;type:text"`
	Phone                    string          `gorm:"column:phone;size:50"`
	AvatarURL                string          `gorm:"column:avatar_url;size:1024"`
	WebsiteURL               string          `gorm:"column:website_url;size:1024"`
	Bio                      string          `gorm:"column:bio;type:text"`
	CreatedAt                string          `gorm:"column:created_at;size:50;not null;<-:create"`
	UpdatedAt                string          `gorm:"column:updated_at;size:50;not null"`
	Banned                   bool            `gorm:"column:banned;default:false"`
	BanReason                string          `gorm:"column:ban_reason;type:text"`
	BannedAt                 string          `gorm:"column:banned_at;size:50"`
	TokenEpoch               int64           `gorm:"column:token_epoch;default:0"`
	AuthClients              AuthClientSlice `gorm:"column:auth_clients;type:mediumtext"`
}

func (DBUser) TableName() string { return "users" }

// DBUserFromRecord 将领域模型转换为 GORM 模型（导出供 migrate 工具使用）。
func DBUserFromRecord(r models.UserRecord) DBUser {
	return dbUserFromRecord(r)
}

func dbUserFromRecord(r models.UserRecord) DBUser {
	return DBUser{
		Account:                  r.Account,
		PasswordHash:             r.PasswordHash,
		Username:                 r.Username,
		Email:                    r.Email,
		Level:                    r.Level,
		SproutCoins:              r.SproutCoins,
		LastCheckInDate:          r.LastCheckInDate,
		LastCheckInAt:            r.LastCheckInAt,
		LastVisitDate:            r.LastVisitDate,
		LastVisitAt:              r.LastVisitAt,
		LastVisitIP:              r.LastVisitIP,
		LastVisitDisplayLocation: r.LastVisitDisplayLocation,
		CheckInTimes:             StringSlice(r.CheckInTimes),
		VisitTimes:               StringSlice(r.VisitTimes),
		SecondaryEmails:          StringSlice(r.SecondaryEmails),
		Phone:                    r.Phone,
		AvatarURL:                r.AvatarURL,
		WebsiteURL:               r.WebsiteURL,
		Bio:                      r.Bio,
		CreatedAt:                r.CreatedAt,
		UpdatedAt:                r.UpdatedAt,
		Banned:                   r.Banned,
		BanReason:                r.BanReason,
		BannedAt:                 r.BannedAt,
		TokenEpoch:               r.TokenEpoch,
		AuthClients:              AuthClientSlice(r.AuthClients),
	}
}

func (d DBUser) toRecord() models.UserRecord {
	return models.UserRecord{
		Account:                  d.Account,
		PasswordHash:             d.PasswordHash,
		Username:                 d.Username,
		Email:                    d.Email,
		Level:                    d.Level,
		SproutCoins:              d.SproutCoins,
		LastCheckInDate:          d.LastCheckInDate,
		LastCheckInAt:            d.LastCheckInAt,
		LastVisitDate:            d.LastVisitDate,
		LastVisitAt:              d.LastVisitAt,
		LastVisitIP:              d.LastVisitIP,
		LastVisitDisplayLocation: d.LastVisitDisplayLocation,
		CheckInTimes:             []string(d.CheckInTimes),
		VisitTimes:               []string(d.VisitTimes),
		SecondaryEmails:          []string(d.SecondaryEmails),
		Phone:                    d.Phone,
		AvatarURL:                d.AvatarURL,
		WebsiteURL:               d.WebsiteURL,
		Bio:                      d.Bio,
		CreatedAt:                d.CreatedAt,
		UpdatedAt:                d.UpdatedAt,
		Banned:                   d.Banned,
		BanReason:                d.BanReason,
		BannedAt:                 d.BannedAt,
		TokenEpoch:               d.TokenEpoch,
		AuthClients:              []models.AuthClientEntry(d.AuthClients),
	}
}

// DBPendingUser 对应 pending_users 表。
type DBPendingUser struct {
	Account      string `gorm:"primaryKey;column:account;size:255"`
	PasswordHash string `gorm:"column:password_hash;not null"`
	Username     string `gorm:"column:username;not null;size:255"`
	Email        string `gorm:"column:email;not null;size:255;index"`
	CodeHash     string `gorm:"column:code_hash;not null"`
	ExpiresAt    string `gorm:"column:expires_at;size:50;not null"`
	CreatedAt    string `gorm:"column:created_at;size:50;not null;<-:create"`
	InviteCode   string `gorm:"column:invite_code;size:32"`
}

func (DBPendingUser) TableName() string { return "pending_users" }

func dbPendingFromModel(p models.PendingUser) DBPendingUser {
	return DBPendingUser{
		Account:      p.Account,
		PasswordHash: p.PasswordHash,
		Username:     p.Username,
		Email:        p.Email,
		CodeHash:     p.CodeHash,
		ExpiresAt:    p.ExpiresAt,
		CreatedAt:    p.CreatedAt,
		InviteCode:   p.InviteCode,
	}
}

func (d DBPendingUser) toModel() models.PendingUser {
	return models.PendingUser{
		Account:      d.Account,
		PasswordHash: d.PasswordHash,
		Username:     d.Username,
		Email:        d.Email,
		CodeHash:     d.CodeHash,
		ExpiresAt:    d.ExpiresAt,
		CreatedAt:    d.CreatedAt,
		InviteCode:   d.InviteCode,
	}
}

// DBResetPassword 对应 password_resets 表。
type DBResetPassword struct {
	Account   string `gorm:"primaryKey;column:account;size:255"`
	Email     string `gorm:"column:email;not null;size:255"`
	CodeHash  string `gorm:"column:code_hash;not null"`
	ExpiresAt string `gorm:"column:expires_at;size:50;not null"`
	CreatedAt string `gorm:"column:created_at;size:50;not null;<-:create"`
}

func (DBResetPassword) TableName() string { return "password_resets" }

func dbResetFromModel(r models.ResetPassword) DBResetPassword {
	return DBResetPassword{
		Account:   r.Account,
		Email:     r.Email,
		CodeHash:  r.CodeHash,
		ExpiresAt: r.ExpiresAt,
		CreatedAt: r.CreatedAt,
	}
}

func (d DBResetPassword) toModel() models.ResetPassword {
	return models.ResetPassword{
		Account:   d.Account,
		Email:     d.Email,
		CodeHash:  d.CodeHash,
		ExpiresAt: d.ExpiresAt,
		CreatedAt: d.CreatedAt,
	}
}

// DBSecondaryEmailVerification 对应 secondary_email_verifications 表。
// 联合主键 (account, email)。
type DBSecondaryEmailVerification struct {
	Account   string `gorm:"primaryKey;column:account;size:255"`
	Email     string `gorm:"primaryKey;column:email;size:255"`
	CodeHash  string `gorm:"column:code_hash;not null"`
	ExpiresAt string `gorm:"column:expires_at;size:50;not null"`
	CreatedAt string `gorm:"column:created_at;size:50;not null;<-:create"`
}

func (DBSecondaryEmailVerification) TableName() string { return "secondary_email_verifications" }

func dbSecondaryFromModel(s models.SecondaryEmailVerification) DBSecondaryEmailVerification {
	return DBSecondaryEmailVerification{
		Account:   s.Account,
		Email:     s.Email,
		CodeHash:  s.CodeHash,
		ExpiresAt: s.ExpiresAt,
		CreatedAt: s.CreatedAt,
	}
}

func (d DBSecondaryEmailVerification) toModel() models.SecondaryEmailVerification {
	return models.SecondaryEmailVerification{
		Account:   d.Account,
		Email:     d.Email,
		CodeHash:  d.CodeHash,
		ExpiresAt: d.ExpiresAt,
		CreatedAt: d.CreatedAt,
	}
}

// DBAppConfig 对应 app_configs 表，存储应用配置键值对（value 为 JSON 文本）。
type DBAppConfig struct {
	ConfigKey   string `gorm:"primaryKey;column:config_key;size:128"`
	ConfigValue string `gorm:"column:config_value;type:text;not null"`
}

func (DBAppConfig) TableName() string { return "app_configs" }

// DBInviteCode 对应 invite_codes 表。
type DBInviteCode struct {
	Code      string `gorm:"primaryKey;column:code;size:32"`
	Note      string `gorm:"column:note;size:512"`
	MaxUses   int    `gorm:"column:max_uses;default:0"`
	Uses      int    `gorm:"column:uses;default:0"`
	ExpiresAt string `gorm:"column:expires_at;size:50"`
	CreatedAt string `gorm:"column:created_at;size:50;not null;<-:create"`
}

func (DBInviteCode) TableName() string { return "invite_codes" }

func dbInviteFromEntry(e InviteEntry) DBInviteCode {
	return DBInviteCode{
		Code:      e.Code,
		Note:      e.Note,
		MaxUses:   e.MaxUses,
		Uses:      e.Uses,
		ExpiresAt: e.ExpiresAt,
		CreatedAt: e.CreatedAt,
	}
}

func (d DBInviteCode) toEntry() InviteEntry {
	return InviteEntry{
		Code:      d.Code,
		Note:      d.Note,
		MaxUses:   d.MaxUses,
		Uses:      d.Uses,
		ExpiresAt: d.ExpiresAt,
		CreatedAt: d.CreatedAt,
	}
}
