package db

import (
	"context"
	"crypto/sha256"
	"database/sql"
	"encoding/hex"
	"errors"
	"strings"
)

func (s *Store) EnsureDefaultAdmin(ctx context.Context) error {
	if err := s.ensureDefaultAdminOn(s.localDB, s.localDialect); err != nil {
		return err
	}
	s.mu.RLock()
	remote, remoteDialect := s.remoteDB, s.remoteDialect
	s.mu.RUnlock()
	if remote != nil && remote != s.localDB {
		if err := s.ensureDefaultAdminOn(remote, remoteDialect); err != nil {
			s.markFailover(err)
		}
	}
	return nil
}

func (s *Store) ensureDefaultAdminOn(conn *sql.DB, d dialect) error {
	if conn == nil {
		return errors.New("database is not available")
	}
	var count int
	if err := conn.QueryRow(d.rebind(`SELECT COUNT(*) FROM admin_users WHERE username = ?`), "admin").Scan(&count); err != nil {
		return err
	}
	if count > 0 {
		return nil
	}
	now := Now()
	_, err := conn.Exec(d.rebind(`INSERT INTO admin_users (username, password_hash, password_changed, created_at, updated_at) VALUES (?, ?, 0, ?, ?)`),
		"admin", passwordHash("admin"), now, now)
	return err
}

func (s *Store) VerifyAdminPassword(ctx context.Context, username, password string) (AdminUser, bool, error) {
	username = strings.TrimSpace(username)
	if username == "" {
		username = "admin"
	}
	user, ok, err := s.verifyAdminPasswordOn(s.localDB, s.localDialect, username, password)
	if err == nil && (ok || user.Username != "") {
		return user, ok, nil
	}
	if err != nil {
		return user, ok, err
	}
	s.mu.RLock()
	remote, remoteDialect := s.remoteDB, s.remoteDialect
	s.mu.RUnlock()
	if remote != nil && remote != s.localDB {
		user, ok, err := s.verifyAdminPasswordOn(remote, remoteDialect, username, password)
		if err != nil {
			s.markFailover(err)
		}
		return user, ok, err
	}
	return user, ok, nil
}

func (s *Store) verifyAdminPasswordOn(conn *sql.DB, d dialect, username, password string) (AdminUser, bool, error) {
	if conn == nil {
		return AdminUser{}, false, errors.New("database is not available")
	}
	var row adminRow
	var changed int
	err := conn.QueryRow(d.rebind(`SELECT id, username, password_hash, password_changed, created_at, updated_at FROM admin_users WHERE username = ?`), username).
		Scan(&row.ID, &row.Username, &row.PasswordHash, &changed, &row.CreatedAt, &row.UpdatedAt)
	if errors.Is(err, sql.ErrNoRows) {
		return AdminUser{}, false, nil
	}
	if err != nil {
		return AdminUser{}, false, err
	}
	row.PasswordChanged = changed == 1
	user := AdminUser{ID: row.ID, Username: row.Username, PasswordChanged: row.PasswordChanged, CreatedAt: row.CreatedAt, UpdatedAt: row.UpdatedAt}
	return user, subtleConstantCompare(row.PasswordHash, password), nil
}

func (s *Store) IsDefaultAdminPassword(ctx context.Context) (bool, error) {
	user, ok, err := s.VerifyAdminPassword(ctx, "admin", "admin")
	if err != nil || !ok {
		return false, err
	}
	return !user.PasswordChanged, nil
}

func (s *Store) ChangeAdminPassword(ctx context.Context, username, current, next string) error {
	_, err := s.ChangeAdminPasswordWithWarning(ctx, username, current, next)
	return err
}

func (s *Store) ChangeAdminPasswordWithWarning(ctx context.Context, username, current, next string) (string, error) {
	next = strings.TrimSpace(next)
	if err := validateAdminPasswordChange(current, next); err != nil {
		return "", err
	}
	username = firstNonEmpty(strings.TrimSpace(username), "admin")
	_, ok, err := s.verifyAdminPasswordOn(s.localDB, s.localDialect, username, current)
	if err != nil {
		return "", err
	}
	if !ok {
		remoteOK, remoteErr := s.verifyRemoteAdminPassword(username, current)
		if remoteErr != nil {
			s.markFailover(remoteErr)
		}
		if !remoteOK {
			return "", errors.New("当前密码不正确")
		}
	}
	hash := passwordHash(next)
	now := Now()
	if err := s.changeAdminPasswordOn(s.localDB, s.localDialect, username, hash, now, true); err != nil {
		return "", err
	}
	s.mu.RLock()
	remote, remoteDialect := s.remoteDB, s.remoteDialect
	s.mu.RUnlock()
	if remote != nil && remote != s.localDB {
		if err := s.changeAdminPasswordOn(remote, remoteDialect, username, hash, now, false); err != nil {
			s.markFailover(err)
			return "远端 MySQL 同步失败，密码已持久化到本地 SQLite", nil
		}
	}
	return "", nil
}

func validateAdminPasswordChange(current, next string) error {
	if next == "" {
		return errors.New("new password is required")
	}
	if len([]rune(next)) < 8 {
		return errors.New("new password must be at least 8 characters")
	}
	if strings.EqualFold(next, "admin") {
		return errors.New("new password cannot be admin")
	}
	if strings.TrimSpace(current) != "" && subtleConstantCompare(passwordHash(current), next) {
		return errors.New("new password must be different from current password")
	}
	return nil
}

func (s *Store) verifyRemoteAdminPassword(username, password string) (bool, error) {
	s.mu.RLock()
	remote, remoteDialect := s.remoteDB, s.remoteDialect
	s.mu.RUnlock()
	if remote == nil || remote == s.localDB {
		return false, nil
	}
	_, ok, err := s.verifyAdminPasswordOn(remote, remoteDialect, username, password)
	return ok, err
}

func (s *Store) changeAdminPasswordOn(conn *sql.DB, d dialect, username, hash, updatedAt string, insertIfMissing bool) error {
	if conn == nil {
		return errors.New("database is not available")
	}
	result, err := conn.Exec(d.rebind(`UPDATE admin_users SET password_hash = ?, password_changed = 1, updated_at = ? WHERE username = ?`), hash, updatedAt, username)
	if err != nil {
		return err
	}
	if rows, _ := result.RowsAffected(); rows > 0 {
		return nil
	}
	if !insertIfMissing {
		return errors.New("admin user not found")
	}
	_, err = conn.Exec(d.rebind(`INSERT INTO admin_users (username, password_hash, password_changed, created_at, updated_at) VALUES (?, ?, 1, ?, ?)`), username, hash, updatedAt, updatedAt)
	return err
}

func passwordHash(password string) string {
	sum := sha256.Sum256([]byte("ymhut-unified|" + strings.TrimSpace(password)))
	return hex.EncodeToString(sum[:])
}

func subtleConstantCompare(hash, password string) bool {
	expected := passwordHash(password)
	return subtleConstantTimeCompare([]byte(hash), []byte(expected)) == 1
}

func subtleConstantTimeCompare(a, b []byte) int {
	if len(a) != len(b) {
		return 0
	}
	var v byte
	for i := range a {
		v |= a[i] ^ b[i]
	}
	if v == 0 {
		return 1
	}
	return 0
}
