package db

import (
	"crypto/rand"
	"database/sql"
	"encoding/hex"
	"errors"
	"time"
)

const adminUserColumns = `id, username, password_hash, must_change_password, must_change_username, totp_secret, totp_enabled, is_global_admin, created_by, created_at`

func scanAdminUser(row *sql.Row) (*AdminUser, error) {
	var u AdminUser
	var createdAt string
	var createdBy sql.NullInt64
	if err := row.Scan(&u.ID, &u.Username, &u.PasswordHash, &u.MustChangePassword, &u.MustChangeUsername, &u.TOTPSecret, &u.TOTPEnabled, &u.IsGlobalAdmin, &createdBy, &createdAt); err != nil {
		if errors.Is(err, sql.ErrNoRows) {
			return nil, nil
		}
		return nil, err
	}
	u.CreatedAt, _ = parseTime(createdAt)
	if createdBy.Valid {
		u.CreatedBy = &createdBy.Int64
	}
	return &u, nil
}

func (d *DB) CountAdminUsers() (int, error) {
	var n int
	err := d.QueryRow(`SELECT COUNT(*) FROM esrv_admin_users`).Scan(&n)
	return n, err
}

// DefaultAdminUsername/Password are the seeded first-run credentials — the admin is
// forced to change both before they can use the rest of the dashboard (see
// AdminUser.MustChangePassword and the login flow).
const (
	DefaultAdminUsername = "admin"
	DefaultAdminPassword = "Password123!"
)

// SeedDefaultAdminIfEmpty creates the default admin account on a brand-new install
// (no admin users yet at all) with must_change_password AND must_change_username set,
// so the well-known default credentials (username "admin") can never be left in place
// silently. This is the one and only place must_change_username is ever set — every
// other admin (delegated, or a global admin created via the delegation flow) picks
// their own username up front and only needs to set their own password.
func (d *DB) SeedDefaultAdminIfEmpty() error {
	n, err := d.CountAdminUsers()
	if err != nil {
		return err
	}
	if n > 0 {
		return nil
	}
	hash, err := HashPassword(DefaultAdminPassword)
	if err != nil {
		return err
	}
	_, err = d.Exec(`INSERT INTO esrv_admin_users (username, password_hash, must_change_password, must_change_username, is_global_admin) VALUES (?, ?, 1, 1, 1)`,
		DefaultAdminUsername, hash)
	return err
}

// CreateAdminUser inserts a new global-admin account (full access, no domain
// restriction) — used by the delegation flow when a global admin grants another user
// global access. mustChangePassword should be true so the admin who set the initial
// password isn't the only one who knows it; must_change_username is always false
// here, since the account was created with the username the new admin will actually
// use (see SeedDefaultAdminIfEmpty for the one exception).
func (d *DB) CreateAdminUser(username, passwordHash string, mustChangePassword bool) (int64, error) {
	res, err := d.Exec(`INSERT INTO esrv_admin_users (username, password_hash, must_change_password, must_change_username, is_global_admin) VALUES (?, ?, ?, 0, 1)`,
		username, passwordHash, mustChangePassword)
	if err != nil {
		return 0, err
	}
	return res.LastInsertId()
}

// CreateScopedAdminUser inserts a new domain-scoped admin (delegated access), owned by
// createdBy, and grants it access to exactly domainIDs — mirrors the delegation flow:
// a scoped admin can create other scoped admins limited to domains within their own.
func (d *DB) CreateScopedAdminUser(username, passwordHash string, createdBy int64, domainIDs []int64) (int64, error) {
	tx, err := d.Begin()
	if err != nil {
		return 0, err
	}
	defer tx.Rollback()

	res, err := tx.Exec(`INSERT INTO esrv_admin_users (username, password_hash, must_change_password, must_change_username, is_global_admin, created_by) VALUES (?, ?, 1, 0, 0, ?)`,
		username, passwordHash, createdBy)
	if err != nil {
		return 0, err
	}
	id, err := res.LastInsertId()
	if err != nil {
		return 0, err
	}
	for _, domainID := range domainIDs {
		if _, err := tx.Exec(`INSERT INTO esrv_admin_domain_access (admin_user_id, domain_id) VALUES (?, ?)`, id, domainID); err != nil {
			return 0, err
		}
	}
	return id, tx.Commit()
}

// ListAllAdminUsers returns every admin account — for a global admin's user-management
// view.
func (d *DB) ListAllAdminUsers() ([]AdminUser, error) {
	rows, err := d.Query(`SELECT ` + adminUserColumns + ` FROM esrv_admin_users ORDER BY username`)
	if err != nil {
		return nil, err
	}
	return scanAdminUsers(rows)
}

// ListScopedAdminUsers returns every non-global admin. Combined with AccessibleDomainIDs
// per user, this lets the caller compute "which of these can I (a scoped admin)
// manage" — the subset check happens in Go since the admin counts here are always
// small (a handful of delegated accounts, not enterprise scale).
func (d *DB) ListScopedAdminUsers() ([]AdminUser, error) {
	rows, err := d.Query(`SELECT ` + adminUserColumns + ` FROM esrv_admin_users WHERE is_global_admin = 0 ORDER BY username`)
	if err != nil {
		return nil, err
	}
	return scanAdminUsers(rows)
}

func scanAdminUsers(rows *sql.Rows) ([]AdminUser, error) {
	defer rows.Close()
	var out []AdminUser
	for rows.Next() {
		var u AdminUser
		var createdAt string
		var createdBy sql.NullInt64
		if err := rows.Scan(&u.ID, &u.Username, &u.PasswordHash, &u.MustChangePassword, &u.MustChangeUsername, &u.TOTPSecret, &u.TOTPEnabled, &u.IsGlobalAdmin, &createdBy, &createdAt); err != nil {
			return nil, err
		}
		u.CreatedAt, _ = parseTime(createdAt)
		if createdBy.Valid {
			u.CreatedBy = &createdBy.Int64
		}
		out = append(out, u)
	}
	return out, rows.Err()
}

// AccessibleDomainIDs returns the domains a scoped admin can see/manage. Meaningless
// for a global admin (they can access everything regardless of this table).
func (d *DB) AccessibleDomainIDs(userID int64) ([]int64, error) {
	rows, err := d.Query(`SELECT domain_id FROM esrv_admin_domain_access WHERE admin_user_id = ?`, userID)
	if err != nil {
		return nil, err
	}
	defer rows.Close()
	var out []int64
	for rows.Next() {
		var id int64
		if err := rows.Scan(&id); err != nil {
			return nil, err
		}
		out = append(out, id)
	}
	return out, rows.Err()
}

// GrantDomainAccess mirrors auto-assigning a newly-created domain to the scoped admin
// who created it.
func (d *DB) GrantDomainAccess(userID, domainID int64) error {
	_, err := d.Exec(`INSERT OR IGNORE INTO esrv_admin_domain_access (admin_user_id, domain_id) VALUES (?, ?)`, userID, domainID)
	return err
}

// SetAdminDomainAccess replaces a scoped admin's entire domain assignment set.
func (d *DB) SetAdminDomainAccess(userID int64, domainIDs []int64) error {
	tx, err := d.Begin()
	if err != nil {
		return err
	}
	defer tx.Rollback()
	if _, err := tx.Exec(`DELETE FROM esrv_admin_domain_access WHERE admin_user_id = ?`, userID); err != nil {
		return err
	}
	for _, id := range domainIDs {
		if _, err := tx.Exec(`INSERT INTO esrv_admin_domain_access (admin_user_id, domain_id) VALUES (?, ?)`, userID, id); err != nil {
			return err
		}
	}
	return tx.Commit()
}

// DeleteAdminUser removes an admin account and everything tied to it.
func (d *DB) DeleteAdminUser(id int64) error {
	tx, err := d.Begin()
	if err != nil {
		return err
	}
	defer tx.Rollback()
	for _, stmt := range []string{
		`DELETE FROM esrv_admin_domain_access WHERE admin_user_id = ?`,
		`DELETE FROM esrv_admin_sessions WHERE user_id = ?`,
		`DELETE FROM esrv_webauthn_credentials WHERE user_id = ?`,
		`DELETE FROM esrv_admin_users WHERE id = ?`,
	} {
		if _, err := tx.Exec(stmt, id); err != nil {
			return err
		}
	}
	return tx.Commit()
}

func (d *DB) GetAdminUserByUsername(username string) (*AdminUser, error) {
	row := d.QueryRow(`SELECT `+adminUserColumns+` FROM esrv_admin_users WHERE lower(username) = lower(?)`, username)
	return scanAdminUser(row)
}

func (d *DB) GetAdminUserByID(id int64) (*AdminUser, error) {
	row := d.QueryRow(`SELECT `+adminUserColumns+` FROM esrv_admin_users WHERE id = ?`, id)
	return scanAdminUser(row)
}

// UpdateAdminCredentials mirrors the forced first-login change for the seeded default
// admin: new username, password hash, and clears must_change_password/
// must_change_username in one step.
func (d *DB) UpdateAdminCredentials(id int64, username, passwordHash string) error {
	_, err := d.Exec(`UPDATE esrv_admin_users SET username = ?, password_hash = ?, must_change_password = 0, must_change_username = 0 WHERE id = ?`, username, passwordHash, id)
	return err
}

// UpdateAdminPasswordClearMustChange mirrors the forced first-login change for a
// delegated admin: password hash only (the username was already chosen when the
// account was created), clearing must_change_password.
func (d *DB) UpdateAdminPasswordClearMustChange(id int64, passwordHash string) error {
	_, err := d.Exec(`UPDATE esrv_admin_users SET password_hash = ?, must_change_password = 0, must_change_username = 0 WHERE id = ?`, passwordHash, id)
	return err
}

func (d *DB) UpdateAdminPassword(id int64, passwordHash string) error {
	_, err := d.Exec(`UPDATE esrv_admin_users SET password_hash = ? WHERE id = ?`, passwordHash, id)
	return err
}

func (d *DB) SetAdminTOTPSecret(id int64, secret string, enabled bool) error {
	_, err := d.Exec(`UPDATE esrv_admin_users SET totp_secret = ?, totp_enabled = ? WHERE id = ?`, secret, enabled, id)
	return err
}

func (d *DB) DisableAdminTOTP(id int64) error {
	_, err := d.Exec(`UPDATE esrv_admin_users SET totp_secret = '', totp_enabled = 0 WHERE id = ?`, id)
	return err
}

// ResetAdminMFA clears every second factor an admin has enrolled — TOTP and every
// registered passkey — e.g. after a lost device, so they can re-enroll from scratch.
// Distinct from DisableAdminTOTP (TOTP only, self-service from /account): this is the
// admin-management action a manager takes on someone else's account (see
// adminWithManageAccess's delegation rule for who's allowed to).
func (d *DB) ResetAdminMFA(id int64) error {
	tx, err := d.Begin()
	if err != nil {
		return err
	}
	defer tx.Rollback()
	if _, err := tx.Exec(`UPDATE esrv_admin_users SET totp_secret = '', totp_enabled = 0 WHERE id = ?`, id); err != nil {
		return err
	}
	if _, err := tx.Exec(`DELETE FROM esrv_webauthn_credentials WHERE user_id = ?`, id); err != nil {
		return err
	}
	return tx.Commit()
}

// --- Sessions ---

func newSessionToken() string {
	b := make([]byte, 32)
	rand.Read(b)
	return hex.EncodeToString(b)
}

// CreateSession mirrors starting a new login session; mfaVerified should be true only
// when the account has no second factor enabled (nothing left to verify) or the second
// factor was just satisfied.
func (d *DB) CreateSession(userID int64, mfaVerified bool, ttl time.Duration) (string, error) {
	token := newSessionToken()
	_, err := d.Exec(`INSERT INTO esrv_admin_sessions (token, user_id, mfa_verified, expires_at) VALUES (?, ?, ?, ?)`,
		token, userID, mfaVerified, time.Now().Add(ttl))
	if err != nil {
		return "", err
	}
	return token, nil
}

func (d *DB) GetSession(token string) (*AdminSession, error) {
	row := d.QueryRow(`SELECT token, user_id, mfa_verified, created_at, expires_at FROM esrv_admin_sessions WHERE token = ?`, token)
	var s AdminSession
	var createdAt, expiresAt string
	if err := row.Scan(&s.Token, &s.UserID, &s.MFAVerified, &createdAt, &expiresAt); err != nil {
		if errors.Is(err, sql.ErrNoRows) {
			return nil, nil
		}
		return nil, err
	}
	s.CreatedAt, _ = parseTime(createdAt)
	s.ExpiresAt, _ = parseTime(expiresAt)
	return &s, nil
}

func (d *DB) MarkSessionMFAVerified(token string) error {
	_, err := d.Exec(`UPDATE esrv_admin_sessions SET mfa_verified = 1 WHERE token = ?`, token)
	return err
}

func (d *DB) DeleteSession(token string) error {
	_, err := d.Exec(`DELETE FROM esrv_admin_sessions WHERE token = ?`, token)
	return err
}

// DeleteExpiredSessions is a lightweight best-effort sweep, called opportunistically
// rather than on a schedule — this admin UI has at most a handful of sessions ever.
func (d *DB) DeleteExpiredSessions() error {
	_, err := d.Exec(`DELETE FROM esrv_admin_sessions WHERE expires_at < ?`, time.Now())
	return err
}

// --- WebAuthn credentials ---

func (d *DB) ListWebAuthnCredentials(userID int64) ([]WebAuthnCredential, error) {
	rows, err := d.Query(`SELECT id, user_id, name, credential_id, credential_data, created_at FROM esrv_webauthn_credentials WHERE user_id = ? ORDER BY created_at`, userID)
	if err != nil {
		return nil, err
	}
	defer rows.Close()
	var out []WebAuthnCredential
	for rows.Next() {
		var c WebAuthnCredential
		var createdAt string
		if err := rows.Scan(&c.ID, &c.UserID, &c.Name, &c.CredentialID, &c.CredentialData, &createdAt); err != nil {
			return nil, err
		}
		c.CreatedAt, _ = parseTime(createdAt)
		out = append(out, c)
	}
	return out, rows.Err()
}

func (d *DB) CreateWebAuthnCredential(userID int64, name, credentialID, credentialData string) error {
	_, err := d.Exec(`INSERT INTO esrv_webauthn_credentials (user_id, name, credential_id, credential_data) VALUES (?, ?, ?, ?)`,
		userID, name, credentialID, credentialData)
	return err
}

func (d *DB) DeleteWebAuthnCredential(id, userID int64) error {
	_, err := d.Exec(`DELETE FROM esrv_webauthn_credentials WHERE id = ? AND user_id = ?`, id, userID)
	return err
}

func (d *DB) CountWebAuthnCredentials(userID int64) (int, error) {
	var n int
	err := d.QueryRow(`SELECT COUNT(*) FROM esrv_webauthn_credentials WHERE user_id = ?`, userID).Scan(&n)
	return n, err
}
