package store

import (
	"database/sql"
	"fmt"
	"log/slog"
	"os"

	_ "modernc.org/sqlite"
)

type SQLiteDialect struct{}

func NewSQLiteStore(path string) (*SQLStore, error) {
	// Apply pragmas via the DSN so every pooled connection gets them — a
	// post-open PRAGMA Exec only affects a single connection. WAL allows
	// concurrent readers alongside the single writer goroutine; busy_timeout
	// rides out brief lock contention; synchronous=NORMAL is durable under WAL
	// and far faster than the FULL default. (:memory: is left untouched —
	// these pragmas are no-ops or harmful for the in-memory test DB.)
	dsn := path
	if path != ":memory:" {
		dsn = fmt.Sprintf("file:%s?_pragma=journal_mode(wal)&_pragma=busy_timeout(5000)&_pragma=synchronous(normal)", path)
	}
	s, err := NewSQLStore("sqlite", dsn, &SQLiteDialect{})
	if err != nil {
		return nil, err
	}
	if path != ":memory:" {
		for _, suffix := range []string{"", "-wal", "-shm"} {
			if err := os.Chmod(path+suffix, 0600); err != nil && !os.IsNotExist(err) {
				slog.Warn("failed to chmod database file", "path", path+suffix, "err", err)
			}
		}
	}
	return s, nil
}

func (d *SQLiteDialect) DriverName() string   { return "sqlite" }
func (d *SQLiteDialect) BoolFalse() string    { return "0" }
func (d *SQLiteDialect) BaselineVersion() int { return 13 }

func (d *SQLiteDialect) CreateTablesSQL() []string {
	return []string{
		`CREATE TABLE IF NOT EXISTS alerts (
			id INTEGER PRIMARY KEY AUTOINCREMENT,
			name TEXT, type TEXT, settings TEXT
		)`,
		`CREATE TABLE IF NOT EXISTS sites (
			id INTEGER PRIMARY KEY AUTOINCREMENT,
			name TEXT DEFAULT 'New Monitor', url TEXT, type TEXT DEFAULT 'http',
			token TEXT, interval INTEGER, alert_id INTEGER,
			check_ssl BOOLEAN DEFAULT 0, threshold INTEGER DEFAULT 7,
			max_retries INTEGER DEFAULT 0, hostname TEXT DEFAULT '',
			port INTEGER DEFAULT 0, timeout INTEGER DEFAULT 0,
			method TEXT DEFAULT 'GET', description TEXT DEFAULT '',
			parent_id INTEGER DEFAULT 0, accepted_codes TEXT DEFAULT '200-299',
			dns_resolve_type TEXT DEFAULT '', dns_server TEXT DEFAULT '',
			ignore_tls BOOLEAN DEFAULT 0, paused BOOLEAN DEFAULT 0,
			regions TEXT DEFAULT ''
		)`,
		`CREATE TABLE IF NOT EXISTS users (
			id INTEGER PRIMARY KEY AUTOINCREMENT,
			username TEXT NOT NULL, public_key TEXT NOT NULL,
			role TEXT DEFAULT 'user'
		)`,
		`CREATE TABLE IF NOT EXISTS check_history (
			id INTEGER PRIMARY KEY AUTOINCREMENT,
			site_id INTEGER NOT NULL, latency_ns INTEGER,
			is_up BOOLEAN, checked_at DATETIME DEFAULT CURRENT_TIMESTAMP,
			node_id TEXT DEFAULT ''
		)`,
		`CREATE INDEX IF NOT EXISTS idx_check_history_site ON check_history(site_id, checked_at DESC)`,
		`CREATE TABLE IF NOT EXISTS nodes (
			id TEXT PRIMARY KEY,
			name TEXT NOT NULL,
			region TEXT DEFAULT '',
			last_seen DATETIME DEFAULT CURRENT_TIMESTAMP,
			version TEXT DEFAULT ''
		)`,
		`CREATE TABLE IF NOT EXISTS logs (
			id INTEGER PRIMARY KEY AUTOINCREMENT,
			message TEXT NOT NULL,
			created_at DATETIME DEFAULT CURRENT_TIMESTAMP
		)`,
		`CREATE TABLE IF NOT EXISTS maintenance_windows (
			id INTEGER PRIMARY KEY AUTOINCREMENT,
			monitor_id INTEGER DEFAULT 0,
			title TEXT NOT NULL,
			description TEXT DEFAULT '',
			type TEXT DEFAULT 'maintenance',
			start_time DATETIME NOT NULL,
			end_time DATETIME,
			created_by TEXT DEFAULT '',
			created_at DATETIME DEFAULT CURRENT_TIMESTAMP
		)`,
		`CREATE TABLE IF NOT EXISTS preferences (
			key TEXT PRIMARY KEY,
			value TEXT NOT NULL
		)`,
		`CREATE TABLE IF NOT EXISTS state_changes (
			id INTEGER PRIMARY KEY AUTOINCREMENT,
			site_id INTEGER NOT NULL,
			from_status TEXT NOT NULL,
			to_status TEXT NOT NULL,
			error_reason TEXT DEFAULT '',
			changed_at DATETIME DEFAULT CURRENT_TIMESTAMP
		)`,
		`CREATE INDEX IF NOT EXISTS idx_state_changes_site ON state_changes(site_id, changed_at DESC)`,
		`CREATE TABLE IF NOT EXISTS alert_health (
			alert_id INTEGER PRIMARY KEY,
			last_send_at DATETIME,
			last_send_ok BOOLEAN DEFAULT 0,
			last_error TEXT DEFAULT '',
			send_count INTEGER DEFAULT 0,
			fail_count INTEGER DEFAULT 0
		)`,
	}
}

func (d *SQLiteDialect) Migrations() []Migration {
	return []Migration{
		{1, "ALTER TABLE sites ADD COLUMN hostname TEXT DEFAULT ''"},
		{2, "ALTER TABLE sites ADD COLUMN port INTEGER DEFAULT 0"},
		{3, "ALTER TABLE sites ADD COLUMN timeout INTEGER DEFAULT 0"},
		{4, "ALTER TABLE sites ADD COLUMN method TEXT DEFAULT 'GET'"},
		{5, "ALTER TABLE sites ADD COLUMN description TEXT DEFAULT ''"},
		{6, "ALTER TABLE sites ADD COLUMN parent_id INTEGER DEFAULT 0"},
		{7, "ALTER TABLE sites ADD COLUMN accepted_codes TEXT DEFAULT '200-299'"},
		{8, "ALTER TABLE sites ADD COLUMN dns_resolve_type TEXT DEFAULT ''"},
		{9, "ALTER TABLE sites ADD COLUMN dns_server TEXT DEFAULT ''"},
		{10, "ALTER TABLE sites ADD COLUMN ignore_tls BOOLEAN DEFAULT 0"},
		{11, "ALTER TABLE sites ADD COLUMN paused BOOLEAN DEFAULT 0"},
		{12, "ALTER TABLE check_history ADD COLUMN node_id TEXT DEFAULT ''"},
		{13, "ALTER TABLE sites ADD COLUMN regions TEXT DEFAULT ''"},
	}
}

func (d *SQLiteDialect) UpsertNodeSQL() string {
	return "INSERT OR REPLACE INTO nodes (id, name, region, last_seen, version) VALUES (?, ?, ?, CURRENT_TIMESTAMP, ?)"
}

func (d *SQLiteDialect) UpsertAlertHealthSQL() string {
	return "INSERT OR REPLACE INTO alert_health (alert_id, last_send_at, last_send_ok, last_error, send_count, fail_count) VALUES (?, ?, ?, ?, ?, ?)"
}

func (d *SQLiteDialect) ResetSequenceOnEmpty(db *sql.DB, table string) {
	var count int
	_ = db.QueryRow("SELECT COUNT(*) FROM " + table).Scan(&count) //nolint:errcheck
	if count == 0 {
		if _, err := db.Exec("DELETE FROM sqlite_sequence WHERE name=?", table); err != nil {
			slog.Debug("sequence cleanup failed", "table", table, "err", err)
		}
	}
}

func (d *SQLiteDialect) ImportWipe(tx *sql.Tx) {
	if _, err := tx.Exec("DELETE FROM sites"); err != nil {
		slog.Debug("import wipe failed", "table", "sites", "err", err)
	}
	if _, err := tx.Exec("DELETE FROM sqlite_sequence WHERE name='sites'"); err != nil {
		slog.Debug("import wipe failed", "table", "sqlite_sequence(sites)", "err", err)
	}
	if _, err := tx.Exec("DELETE FROM alerts"); err != nil {
		slog.Debug("import wipe failed", "table", "alerts", "err", err)
	}
	if _, err := tx.Exec("DELETE FROM sqlite_sequence WHERE name='alerts'"); err != nil {
		slog.Debug("import wipe failed", "table", "sqlite_sequence(alerts)", "err", err)
	}
	if _, err := tx.Exec("DELETE FROM maintenance_windows"); err != nil {
		slog.Debug("import wipe failed", "table", "maintenance_windows", "err", err)
	}
	if _, err := tx.Exec("DELETE FROM sqlite_sequence WHERE name='maintenance_windows'"); err != nil {
		slog.Debug("import wipe failed", "table", "sqlite_sequence(maintenance_windows)", "err", err)
	}
	if _, err := tx.Exec("DELETE FROM check_history"); err != nil {
		slog.Debug("import wipe failed", "table", "check_history", "err", err)
	}
	if _, err := tx.Exec("DELETE FROM state_changes"); err != nil {
		slog.Debug("import wipe failed", "table", "state_changes", "err", err)
	}
	if _, err := tx.Exec("DELETE FROM alert_health"); err != nil {
		slog.Debug("import wipe failed", "table", "alert_health", "err", err)
	}
}

func (d *SQLiteDialect) ImportWipeUsers(tx *sql.Tx) {
	if _, err := tx.Exec("DELETE FROM users"); err != nil {
		slog.Debug("import wipe failed", "table", "users", "err", err)
	}
	if _, err := tx.Exec("DELETE FROM sqlite_sequence WHERE name='users'"); err != nil {
		slog.Debug("import wipe failed", "table", "sqlite_sequence(users)", "err", err)
	}
}

func (d *SQLiteDialect) ImportResetSequences(tx *sql.Tx) {}
