package system

import (
	"context"
	"errors"
	"time"

	"kra/internal/biz"

	"gorm.io/gorm"
)

// DatabaseProvider is the smaller seam used by security settings, which also
// need to work before the primary database is initialized.
type DatabaseProvider interface {
	DB() *gorm.DB
	DatabaseReady() bool
}

type SecurityConfigPO struct {
	ID                         uint `gorm:"primaryKey"`
	CreatedAt                  time.Time
	UpdatedAt                  time.Time
	DeletedAt                  gorm.DeletedAt `gorm:"index"`
	CaptchaOpen                int            `gorm:"default:0"`
	CaptchaTimeout             int            `gorm:"default:3600"`
	KeyLong                    int            `gorm:"default:6"`
	ImgWidth                   int            `gorm:"default:240"`
	ImgHeight                  int            `gorm:"default:80"`
	PwdMinLength               int            `gorm:"default:8"`
	PwdRequireUpper            bool           `gorm:"default:false"`
	PwdRequireLower            bool           `gorm:"default:false"`
	PwdRequireDigit            bool           `gorm:"default:false"`
	PwdRequireSpecial          bool           `gorm:"default:false"`
	LimitEnable                bool           `gorm:"default:false"`
	LimitWindow                int            `gorm:"default:60"`
	LimitCount                 int            `gorm:"default:30"`
	LockEnable                 bool           `gorm:"default:false"`
	LockThreshold              int            `gorm:"default:5"`
	LockDuration               int            `gorm:"default:30"`
	PwdExpireEnable            bool           `gorm:"default:false"`
	PwdExpireDays              int            `gorm:"default:90"`
	ForceNewUserChangePassword bool           `gorm:"default:false"`
}

func (SecurityConfigPO) TableName() string { return "sys_security_config" }

func DefaultSecurityConfig() SecurityConfigPO {
	return SecurityConfigPO{ID: 1, CaptchaTimeout: 3600, KeyLong: 6, ImgWidth: 240, ImgHeight: 80, PwdMinLength: 8, LimitWindow: 60, LimitCount: 30, LockThreshold: 5, LockDuration: 30, PwdExpireDays: 90}
}

type securityRepo struct{ data DatabaseProvider }

func NewSecurityRepo(data DatabaseProvider) biz.SecurityRepo { return &securityRepo{data: data} }

func securityFromPO(v SecurityConfigPO) *biz.SecurityConfig {
	return &biz.SecurityConfig{ID: v.ID, CreatedAt: v.CreatedAt, UpdatedAt: v.UpdatedAt, CaptchaOpen: v.CaptchaOpen, CaptchaTimeout: v.CaptchaTimeout, KeyLong: v.KeyLong, ImgWidth: v.ImgWidth, ImgHeight: v.ImgHeight, PwdMinLength: v.PwdMinLength, PwdRequireUpper: v.PwdRequireUpper, PwdRequireLower: v.PwdRequireLower, PwdRequireDigit: v.PwdRequireDigit, PwdRequireSpecial: v.PwdRequireSpecial, LimitEnable: v.LimitEnable, LimitWindow: v.LimitWindow, LimitCount: v.LimitCount, LockEnable: v.LockEnable, LockThreshold: v.LockThreshold, LockDuration: v.LockDuration, PwdExpireEnable: v.PwdExpireEnable, PwdExpireDays: v.PwdExpireDays, ForceNewUserChangePassword: v.ForceNewUserChangePassword}
}

func securityToPO(v *biz.SecurityConfig) SecurityConfigPO {
	return SecurityConfigPO{ID: 1, CreatedAt: v.CreatedAt, UpdatedAt: v.UpdatedAt, CaptchaOpen: v.CaptchaOpen, CaptchaTimeout: v.CaptchaTimeout, KeyLong: v.KeyLong, ImgWidth: v.ImgWidth, ImgHeight: v.ImgHeight, PwdMinLength: v.PwdMinLength, PwdRequireUpper: v.PwdRequireUpper, PwdRequireLower: v.PwdRequireLower, PwdRequireDigit: v.PwdRequireDigit, PwdRequireSpecial: v.PwdRequireSpecial, LimitEnable: v.LimitEnable, LimitWindow: v.LimitWindow, LimitCount: v.LimitCount, LockEnable: v.LockEnable, LockThreshold: v.LockThreshold, LockDuration: v.LockDuration, PwdExpireEnable: v.PwdExpireEnable, PwdExpireDays: v.PwdExpireDays, ForceNewUserChangePassword: v.ForceNewUserChangePassword}
}

func (r *securityRepo) SecurityConfig(ctx context.Context) (*biz.SecurityConfig, error) {
	if !r.data.DatabaseReady() {
		po := DefaultSecurityConfig()
		po.ID = 0
		return securityFromPO(po), errors.New("数据库未初始化")
	}
	db := r.data.DB().WithContext(ctx)
	var po SecurityConfigPO
	err := db.First(&po, 1).Error
	if errors.Is(err, gorm.ErrRecordNotFound) {
		po = DefaultSecurityConfig()
		err = db.Create(&po).Error
	}
	if err != nil {
		return nil, err
	}
	return securityFromPO(po), nil
}

func (r *securityRepo) SaveSecurityConfig(ctx context.Context, v *biz.SecurityConfig) error {
	po := securityToPO(v)
	if err := r.data.DB().WithContext(ctx).Save(&po).Error; err != nil {
		return err
	}
	v.ID, v.CreatedAt, v.UpdatedAt = po.ID, po.CreatedAt, po.UpdatedAt
	return nil
}

func (r *securityRepo) BackfillPasswordUpdatedAt(ctx context.Context, at time.Time) error {
	return r.data.DB().WithContext(ctx).Table("sys_users").Where("password_updated_at IS NULL").Update("password_updated_at", at).Error
}
