package model

import (
	"crypto/rand"
	"math/big"
	"strings"

	"golang.org/x/crypto/bcrypt"
	"gorm.io/gorm/clause"

	"chaitin.cn/patronus/safeline-2/management/webserver/pkg/constants"
	"chaitin.cn/patronus/safeline-2/management/webserver/pkg/database"
)

type User struct {
	Base
	Username string `gorm:"uniqueIndex;not null"`
	Password string `gorm:"-"` // transient, not stored
	PasswordHash string
	Comment  string
	Role     string `gorm:"default:viewer"` // admin, operator, viewer

	TFAEnabled    bool   `gorm:"column:tfa_enabled;default:true"`
	TFASecret     string `gorm:"column:tfa_secret"`
	LastLoginTime int64  `gorm:"default:0"`
	IsEnabled     bool   `gorm:"default:true"`
}

// HasPermission checks if the user role has the required permission level.
// admin > operator > viewer
func (u *User) HasPermission(requiredRole string) bool {
	roleHierarchy := map[string]int{
		"viewer":   1,
		"operator": 2,
		"admin":    3,
	}
	userLevel, ok := roleHierarchy[u.Role]
	if !ok {
		return false
	}
	requiredLevel, ok := roleHierarchy[requiredRole]
	if !ok {
		return false
	}
	return userLevel >= requiredLevel
}

// CheckPassword verifies a plaintext password against the bcrypt hash.
func (u *User) CheckPassword(password string) bool {
	if u.PasswordHash == "" {
		return false
	}
	err := bcrypt.CompareHashAndPassword([]byte(u.PasswordHash), []byte(password))
	return err == nil
}

// SetPassword hashes and sets the password.
func (u *User) SetPassword(password string) error {
	hash, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
	if err != nil {
		return err
	}
	u.PasswordHash = string(hash)
	return nil
}

// GenerateRandomPassword generates a random 16-char password.
func GenerateRandomPassword() (string, error) {
	const chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!@#$%^&*"
	var sb strings.Builder
	for i := 0; i < 16; i++ {
		n, err := rand.Int(rand.Reader, big.NewInt(int64(len(chars))))
		if err != nil {
			return "", err
		}
		sb.WriteByte(chars[n.Int64()])
	}
	return sb.String(), nil
}

func initAdminUser() error {
	db := database.GetDB()

	// Check if admin already exists
	var existing User
	res := db.Where(&User{Username: constants.SuperUser}).First(&existing)
	if res.RowsAffected > 0 {
		return nil
	}

	// Generate random password for admin
	defaultPassword, err := GenerateRandomPassword()
	if err != nil {
		return err
	}

	hash, err := bcrypt.GenerateFromPassword([]byte(defaultPassword), bcrypt.DefaultCost)
	if err != nil {
		return err
	}

	admin := User{
		Username:     constants.SuperUser,
		PasswordHash: string(hash),
		Role:         "admin",
		Comment:      "Default admin user",
		TFAEnabled:   true,
		IsEnabled:    true,
	}

	db.Clauses(clause.OnConflict{DoNothing: true}).Create(&admin)

	// Log the default password (will appear in container logs)
	// In production, the mgt-cli reset-admin command handles this
	_ = defaultPassword

	return nil
}
