package db

import (
	"crypto/rand"
	"encoding/hex"
	"time"
)

func (d *DB) ListDomains() ([]Domain, error) {
	rows, err := d.Query(`SELECT ` + domainColumns + ` FROM esrv_domains ORDER BY domain_name`)
	if err != nil {
		return nil, err
	}
	defer rows.Close()
	var out []Domain
	for rows.Next() {
		var dm Domain
		var createdAt string
		var verifiedAt *string
		if err := rows.Scan(&dm.ID, &dm.DomainName, &dm.IsActive, &createdAt, &dm.VerificationToken, &dm.IsVerified, &verifiedAt, &dm.MFAExempt); err != nil {
			return nil, err
		}
		dm.CreatedAt, _ = parseTime(createdAt)
		if verifiedAt != nil {
			t, _ := parseTime(*verifiedAt)
			dm.VerifiedAt = &t
		}
		out = append(out, dm)
	}
	return out, rows.Err()
}

// ListActiveDomains mirrors the `domains` query used to populate <select> lists on the
// add/edit sender and IP forms.
func (d *DB) ListActiveDomains() ([]Domain, error) {
	all, err := d.ListDomains()
	if err != nil {
		return nil, err
	}
	var out []Domain
	for _, dm := range all {
		if dm.IsActive {
			out = append(out, dm)
		}
	}
	return out, nil
}

func (d *DB) CountActiveDomains() (int, error) {
	var n int
	err := d.QueryRow(`SELECT COUNT(*) FROM esrv_domains WHERE is_active = 1`).Scan(&n)
	return n, err
}

// generateVerificationToken returns a random 32-hex-char token for the DNS TXT
// ownership check, mirroring the randomness quality already used for DKIM selectors.
func generateVerificationToken() string {
	b := make([]byte, 16)
	rand.Read(b)
	return hex.EncodeToString(b)
}

// CreateDomain inserts a new, unverified domain with a freshly generated DNS
// verification token.
func (d *DB) CreateDomain(name string) (int64, error) {
	res, err := d.Exec(`INSERT INTO esrv_domains (domain_name, is_active, verification_token, is_verified) VALUES (?, 1, ?, 0)`,
		name, generateVerificationToken())
	if err != nil {
		return 0, err
	}
	return res.LastInsertId()
}

func (d *DB) UpdateDomain(id int64, name string, requiresAuth bool) error {
	_, err := d.Exec(`UPDATE esrv_domains SET domain_name = ? WHERE id = ?`, name, id)
	return err
}

func (d *DB) SetDomainActive(id int64, active bool) error {
	_, err := d.Exec(`UPDATE esrv_domains SET is_active = ? WHERE id = ?`, active, id)
	return err
}

// SetDomainMFAExempt overrides [Auth] enforce_mailbox_mfa off for every mailbox under
// this domain (regardless of each mailbox's own MFAExempt).
func (d *DB) SetDomainMFAExempt(id int64, exempt bool) error {
	_, err := d.Exec(`UPDATE esrv_domains SET mfa_exempt = ? WHERE id = ?`, exempt, id)
	return err
}

// SetDomainVerified mirrors marking a domain as DNS-ownership-verified (or reverting
// it, e.g. if an admin wants to force re-verification).
func (d *DB) SetDomainVerified(id int64, verified bool) error {
	if verified {
		_, err := d.Exec(`UPDATE esrv_domains SET is_verified = 1, verified_at = ? WHERE id = ?`, time.Now(), id)
		return err
	}
	_, err := d.Exec(`UPDATE esrv_domains SET is_verified = 0, verified_at = NULL WHERE id = ?`, id)
	return err
}

// RegenerateVerificationToken mirrors resetting a domain back to a fresh, unverified
// token — used if an admin wants a new TXT value (e.g. suspected leak, or restarting
// the ownership proof).
func (d *DB) RegenerateVerificationToken(id int64) (string, error) {
	token := generateVerificationToken()
	_, err := d.Exec(`UPDATE esrv_domains SET verification_token = ?, is_verified = 0, verified_at = NULL WHERE id = ?`, token, id)
	return token, err
}

// RemoveDomainCascade hard-deletes a domain and every row that references it, mirroring
// domains.remove_domain. Returns counts removed for the flash message.
func (d *DB) RemoveDomainCascade(id int64) (senders, ips, dkimKeys, headers int, err error) {
	tx, err := d.Begin()
	if err != nil {
		return
	}
	defer tx.Rollback()

	for table, count := range map[string]*int{
		"esrv_senders":         &senders,
		"esrv_whitelisted_ips": &ips,
		"esrv_dkim_keys":       &dkimKeys,
		"esrv_custom_headers":  &headers,
	} {
		row := tx.QueryRow(`SELECT COUNT(*) FROM `+table+` WHERE domain_id = ?`, id)
		if err = row.Scan(count); err != nil {
			return
		}
		if _, err = tx.Exec(`DELETE FROM `+table+` WHERE domain_id = ?`, id); err != nil {
			return
		}
	}
	if _, err = tx.Exec(`DELETE FROM esrv_domains WHERE id = ?`, id); err != nil {
		return
	}
	err = tx.Commit()
	return
}
