package storage

import (
	"crypto/rand"
	"encoding/base64"
	"encoding/json"
	"errors"
	"os"
	"strings"
	"sync"

	"gorm.io/gorm"
	"gorm.io/gorm/clause"

	"sproutgate-backend/internal/models"
)

// ─── 配置结构体（公开 API 不变）────────────────────────────────────────────────

type AdminConfig struct {
	Token string `json:"token"`
}

type AuthConfig struct {
	JWTSecret string `json:"jwtSecret"`
	Issuer    string `json:"issuer"`
}

type EmailConfig struct {
	FromName    string `json:"fromName"`
	FromAddress string `json:"fromAddress"`
	Username    string `json:"username"`
	Password    string `json:"password"`
	SMTPHost    string `json:"smtpHost"`
	SMTPPort    int    `json:"smtpPort"`
	Encryption  string `json:"encryption"`
}

type CheckInConfig struct {
	RewardCoins int `json:"rewardCoins"`
}

// ─── Store ─────────────────────────────────────────────────────────────────────

type Store struct {
	db                 *gorm.DB
	adminToken         string
	jwtSecret          []byte
	issuer             string
	emailConfig        EmailConfig
	checkInConfig      CheckInConfig
	registrationConfig RegistrationConfig
	mu                 sync.RWMutex
}

// NewStore 接收已建立连接的 *gorm.DB，自动迁移表结构并加载配置。
func NewStore(db *gorm.DB) (*Store, error) {
	if err := db.AutoMigrate(
		&DBUser{},
		&DBPendingUser{},
		&DBResetPassword{},
		&DBSecondaryEmailVerification{},
		&DBAppConfig{},
		&DBInviteCode{},
		&DBProfileLike{},
		&DBProfileLikeDailyQuota{},
	); err != nil {
		return nil, err
	}

	store := &Store{db: db}

	if err := store.loadOrCreateAdminConfig(); err != nil {
		return nil, err
	}
	if err := store.loadOrCreateAuthConfig(); err != nil {
		return nil, err
	}
	if err := store.loadOrCreateEmailConfig(); err != nil {
		return nil, err
	}
	if err := store.loadOrCreateCheckInConfig(); err != nil {
		return nil, err
	}
	if err := store.loadOrCreateRegistrationConfig(); err != nil {
		return nil, err
	}

	return store, nil
}

// ─── 配置辅助 ────────────────────────────────────────────────────────────────

func (s *Store) getConfig(key string, target any) (bool, error) {
	var row DBAppConfig
	result := s.db.First(&row, "config_key = ?", key)
	if errors.Is(result.Error, gorm.ErrRecordNotFound) {
		return false, nil
	}
	if result.Error != nil {
		return false, result.Error
	}
	return true, json.Unmarshal([]byte(row.ConfigValue), target)
}

func (s *Store) setConfig(key string, value any) error {
	raw, err := json.Marshal(value)
	if err != nil {
		return err
	}
	row := DBAppConfig{ConfigKey: key, ConfigValue: string(raw)}
	return s.db.Clauses(clause.OnConflict{UpdateAll: true}).Create(&row).Error
}

// ─── Admin 配置 ───────────────────────────────────────────────────────────────

func (s *Store) AdminToken() string { return s.adminToken }

func (s *Store) loadOrCreateAdminConfig() error {
	var cfg AdminConfig
	found, err := s.getConfig("admin", &cfg)
	if err != nil {
		return err
	}
	if !found || strings.TrimSpace(cfg.Token) == "" {
		token, err := generateToken()
		if err != nil {
			return err
		}
		cfg.Token = token
		if err := s.setConfig("admin", cfg); err != nil {
			return err
		}
	}
	s.adminToken = cfg.Token
	return nil
}

// ─── Auth 配置 ────────────────────────────────────────────────────────────────

func (s *Store) JWTSecret() []byte { return s.jwtSecret }
func (s *Store) JWTIssuer() string  { return s.issuer }

func (s *Store) loadOrCreateAuthConfig() error {
	var cfg AuthConfig
	found, err := s.getConfig("auth", &cfg)
	if err != nil {
		return err
	}
	secretBytes, decodeErr := base64.StdEncoding.DecodeString(cfg.JWTSecret)
	if !found || decodeErr != nil || len(secretBytes) == 0 {
		secretBytes, err = generateSecret()
		if err != nil {
			return err
		}
		cfg.JWTSecret = base64.StdEncoding.EncodeToString(secretBytes)
	}
	if strings.TrimSpace(cfg.Issuer) == "" {
		cfg.Issuer = "sproutgate"
	}
	if err := s.setConfig("auth", cfg); err != nil {
		return err
	}
	s.jwtSecret = secretBytes
	s.issuer = cfg.Issuer
	return nil
}

// ─── Email 配置 ───────────────────────────────────────────────────────────────

func (s *Store) EmailConfig() EmailConfig { return s.emailConfig }

func (s *Store) loadOrCreateEmailConfig() error {
	var cfg EmailConfig
	found, err := s.getConfig("email", &cfg)
	if err != nil {
		return err
	}
	changed := !found
	if strings.TrimSpace(cfg.FromName) == "" {
		cfg.FromName = "萌芽账户认证中心"
		changed = true
	}
	if strings.TrimSpace(cfg.FromAddress) == "" {
		cfg.FromAddress = "notice@smyhub.com"
		changed = true
	}
	if strings.TrimSpace(cfg.Username) == "" {
		cfg.Username = cfg.FromAddress
		changed = true
	}
	if strings.TrimSpace(cfg.SMTPHost) == "" {
		cfg.SMTPHost = "smtp.qiye.aliyun.com"
		changed = true
	}
	if cfg.SMTPPort == 0 {
		cfg.SMTPPort = 465
		changed = true
	}
	if strings.TrimSpace(cfg.Encryption) == "" {
		cfg.Encryption = "SSL"
		changed = true
	}
	if changed {
		if err := s.setConfig("email", cfg); err != nil {
			return err
		}
	}
	s.emailConfig = cfg
	return nil
}

// ─── CheckIn 配置 ─────────────────────────────────────────────────────────────

func (s *Store) CheckInConfig() CheckInConfig {
	s.mu.RLock()
	defer s.mu.RUnlock()
	cfg := s.checkInConfig
	if cfg.RewardCoins <= 0 {
		cfg.RewardCoins = 1
	}
	return cfg
}

func (s *Store) UpdateCheckInConfig(cfg CheckInConfig) error {
	if cfg.RewardCoins <= 0 {
		cfg.RewardCoins = 1
	}
	if err := s.setConfig("checkin", cfg); err != nil {
		return err
	}
	s.mu.Lock()
	s.checkInConfig = cfg
	s.mu.Unlock()
	return nil
}

func (s *Store) loadOrCreateCheckInConfig() error {
	var cfg CheckInConfig
	found, err := s.getConfig("checkin", &cfg)
	if err != nil {
		return err
	}
	if !found || cfg.RewardCoins <= 0 {
		cfg.RewardCoins = 1
		if err := s.setConfig("checkin", cfg); err != nil {
			return err
		}
	}
	s.checkInConfig = cfg
	return nil
}

// ─── 用户 CRUD ────────────────────────────────────────────────────────────────

func (s *Store) ListUsers() ([]models.UserRecord, error) {
	var rows []DBUser
	if err := s.db.Find(&rows).Error; err != nil {
		return nil, err
	}
	users := make([]models.UserRecord, 0, len(rows))
	for _, row := range rows {
		users = append(users, row.toRecord())
	}
	return users, nil
}

func (s *Store) GetUser(account string) (models.UserRecord, bool, error) {
	var row DBUser
	result := s.db.First(&row, "account = ?", account)
	if errors.Is(result.Error, gorm.ErrRecordNotFound) {
		return models.UserRecord{}, false, nil
	}
	if result.Error != nil {
		return models.UserRecord{}, false, result.Error
	}
	return row.toRecord(), true, nil
}

func (s *Store) CreateUser(record models.UserRecord) error {
	if record.CreatedAt == "" {
		record.CreatedAt = models.NowISO()
	}
	record.UpdatedAt = record.CreatedAt

	row := dbUserFromRecord(record)
	result := s.db.Create(&row)
	if result.Error != nil {
		if strings.Contains(result.Error.Error(), "Duplicate entry") ||
			strings.Contains(result.Error.Error(), "duplicate key") {
			return errors.New("account already exists")
		}
		return result.Error
	}
	return nil
}

func (s *Store) SaveUser(record models.UserRecord) error {
	record.UpdatedAt = models.NowISO()
	row := dbUserFromRecord(record)
	return s.db.Save(&row).Error
}

func (s *Store) DeleteUser(account string) error {
	return s.db.Delete(&DBUser{}, "account = ?", account).Error
}

// ─── RecordAuthClient ─────────────────────────────────────────────────────────

func (s *Store) RecordAuthClient(account string, clientID string, displayName string) (models.UserRecord, error) {
	if clientID == "" {
		return models.UserRecord{}, errors.New("client id required")
	}

	var row DBUser
	result := s.db.First(&row, "account = ?", account)
	if errors.Is(result.Error, gorm.ErrRecordNotFound) {
		return models.UserRecord{}, os.ErrNotExist
	}
	if result.Error != nil {
		return models.UserRecord{}, result.Error
	}

	now := models.NowISO()
	displayName = models.ClampAuthClientName(displayName)
	clients := []models.AuthClientEntry(row.AuthClients)
	found := false
	for i := range clients {
		if clients[i].ClientID == clientID {
			clients[i].LastSeenAt = now
			if displayName != "" {
				clients[i].DisplayName = displayName
			}
			found = true
			break
		}
	}
	if !found {
		clients = append(clients, models.AuthClientEntry{
			ClientID:    clientID,
			DisplayName: displayName,
			FirstSeenAt: now,
			LastSeenAt:  now,
		})
	}
	row.AuthClients = AuthClientSlice(clients)
	row.UpdatedAt = now

	if err := s.db.Save(&row).Error; err != nil {
		return models.UserRecord{}, err
	}
	return row.toRecord(), nil
}

// ─── RecordVisit ──────────────────────────────────────────────────────────────

func (s *Store) RecordVisit(account string, today string, at string) (models.UserRecord, bool, error) {
	var row DBUser
	result := s.db.First(&row, "account = ?", account)
	if errors.Is(result.Error, gorm.ErrRecordNotFound) {
		return models.UserRecord{}, false, os.ErrNotExist
	}
	if result.Error != nil {
		return models.UserRecord{}, false, result.Error
	}

	visitTimes := []string(row.VisitTimes)
	if row.LastVisitDate == today || models.HasActivityDate(visitTimes, today) {
		return row.toRecord(), false, nil
	}

	if strings.TrimSpace(at) == "" {
		at = models.CurrentActivityTime()
	}
	row.LastVisitDate = today
	row.LastVisitAt = at
	row.VisitTimes = StringSlice(append(visitTimes, at))
	if row.CreatedAt == "" {
		row.CreatedAt = models.NowISO()
	}
	row.UpdatedAt = models.NowISO()

	if err := s.db.Save(&row).Error; err != nil {
		return models.UserRecord{}, false, err
	}
	return row.toRecord(), true, nil
}

// ─── UpdateLastVisitMeta ─────────────────────────────────────────────────────

const maxLastVisitIPLen = 45
const maxLastVisitDisplayLocationLen = 512

func clampVisitMeta(ip, displayLocation string) (string, string) {
	ip = strings.TrimSpace(ip)
	displayLocation = strings.TrimSpace(displayLocation)
	if len(ip) > maxLastVisitIPLen {
		ip = ip[:maxLastVisitIPLen]
	}
	if len(displayLocation) > maxLastVisitDisplayLocationLen {
		displayLocation = displayLocation[:maxLastVisitDisplayLocationLen]
	}
	return ip, displayLocation
}

func (s *Store) UpdateLastVisitMeta(account string, ip string, displayLocation string) (models.UserRecord, error) {
	ip, displayLocation = clampVisitMeta(ip, displayLocation)
	if ip == "" && displayLocation == "" {
		rec, found, err := s.GetUser(account)
		if err != nil {
			return models.UserRecord{}, err
		}
		if !found {
			return models.UserRecord{}, os.ErrNotExist
		}
		return rec, nil
	}

	var row DBUser
	result := s.db.First(&row, "account = ?", account)
	if errors.Is(result.Error, gorm.ErrRecordNotFound) {
		return models.UserRecord{}, os.ErrNotExist
	}
	if result.Error != nil {
		return models.UserRecord{}, result.Error
	}

	if ip != "" {
		row.LastVisitIP = ip
	}
	if displayLocation != "" {
		row.LastVisitDisplayLocation = displayLocation
	}
	row.UpdatedAt = models.NowISO()

	if err := s.db.Save(&row).Error; err != nil {
		return models.UserRecord{}, err
	}
	return row.toRecord(), nil
}

// ─── CheckIn ─────────────────────────────────────────────────────────────────

func (s *Store) CheckIn(account string, today string, at string) (models.UserRecord, int, bool, error) {
	var row DBUser
	result := s.db.First(&row, "account = ?", account)
	if errors.Is(result.Error, gorm.ErrRecordNotFound) {
		return models.UserRecord{}, 0, false, os.ErrNotExist
	}
	if result.Error != nil {
		return models.UserRecord{}, 0, false, result.Error
	}

	checkInTimes := []string(row.CheckInTimes)
	if row.LastCheckInDate == today || models.HasActivityDate(checkInTimes, today) {
		return row.toRecord(), 0, true, nil
	}

	s.mu.RLock()
	reward := s.checkInConfig.RewardCoins
	s.mu.RUnlock()
	if reward <= 0 {
		reward = 1
	}

	row.SproutCoins += reward
	row.LastCheckInDate = today
	if strings.TrimSpace(at) == "" {
		at = models.CurrentActivityTime()
	}
	row.LastCheckInAt = at
	row.CheckInTimes = StringSlice(append(checkInTimes, at))
	if row.CreatedAt == "" {
		row.CreatedAt = models.NowISO()
	}
	row.UpdatedAt = models.NowISO()

	if err := s.db.Save(&row).Error; err != nil {
		return models.UserRecord{}, 0, false, err
	}
	return row.toRecord(), reward, false, nil
}

// ─── 工具函数 ─────────────────────────────────────────────────────────────────

func generateSecret() ([]byte, error) {
	secret := make([]byte, 32)
	_, err := rand.Read(secret)
	return secret, err
}

func generateToken() (string, error) {
	secret, err := generateSecret()
	if err != nil {
		return "", err
	}
	return base64.RawURLEncoding.EncodeToString(secret), nil
}
