import Database from "better-sqlite3";
import { mkdirSync } from "node:fs";
import { join, dirname } from "node:path";
import { fileURLToPath } from "node:url";

const __dirname = dirname(fileURLToPath(import.meta.url));
const DATA_DIR = join(__dirname, "..", "data");
const DB_PATH = join(DATA_DIR, "alerts.db");

let _db: Database.Database | null = null;

// ─── Types ────────────────────────────────────────────────────────────────────

export type TargetType = "host" | "service" | "network" | "site";
export type Severity = "warning" | "critical";
export type AlertEventKind = "firing" | "resolved";

export interface ThresholdCondition {
  kind: "threshold";
  metric: "disk_percent" | "cpu_percent" | "memory_percent" | "response_time_ms" | "latency_ms" | "ssl_days_remaining";
  operator: "gt" | "lt" | "gte" | "lte";
  value: number;
}

export interface StateCondition {
  kind: "state";
  states: string[];
}

export type RuleCondition = ThresholdCondition | StateCondition;

export interface AlertRule {
  id: string;
  name: string;
  enabled: boolean;
  target_type: TargetType;
  target_id: string | null;
  condition: RuleCondition;
  severity: Severity;
  cooldown_seconds: number;
  channels: string[];
  created_at: string;
  updated_at: string;
}

export interface AlertState {
  rule_id: string;
  target_key: string;
  state: "ok" | "firing";
  fired_at: string | null;
  last_notified_at: string | null;
}

export interface AlertHistoryRow {
  id: string;
  rule_id: string;
  rule_name: string;
  target_key: string;
  target_name: string | null;
  event: AlertEventKind;
  severity: Severity;
  message: string;
  fired_at: string;
  resolved_at: string | null;
  created_at: string;
}

interface RawAlertRule {
  id: string;
  name: string;
  enabled: number;
  target_type: string;
  target_id: string | null;
  condition: string;
  severity: string;
  cooldown_seconds: number;
  channels: string;
  created_at: string;
  updated_at: string;
}

function getDb(): Database.Database {
  if (_db) return _db;
  mkdirSync(DATA_DIR, { recursive: true });
  _db = new Database(DB_PATH);
  _db.pragma("journal_mode = WAL");
  _db.exec(`
    CREATE TABLE IF NOT EXISTS alert_rules (
      id              TEXT PRIMARY KEY,
      name            TEXT NOT NULL,
      enabled         INTEGER NOT NULL DEFAULT 1,
      target_type     TEXT NOT NULL,
      target_id       TEXT,
      condition       TEXT NOT NULL,
      severity        TEXT NOT NULL DEFAULT 'warning',
      cooldown_seconds INTEGER NOT NULL DEFAULT 300,
      channels        TEXT NOT NULL DEFAULT '["notifications","chronicle"]',
      created_at      TEXT NOT NULL,
      updated_at      TEXT NOT NULL
    );

    CREATE TABLE IF NOT EXISTS alert_state (
      rule_id         TEXT NOT NULL,
      target_key      TEXT NOT NULL,
      state           TEXT NOT NULL DEFAULT 'ok',
      fired_at        TEXT,
      last_notified_at TEXT,
      PRIMARY KEY (rule_id, target_key)
    );

    CREATE TABLE IF NOT EXISTS alert_history (
      id          TEXT PRIMARY KEY,
      rule_id     TEXT NOT NULL,
      rule_name   TEXT NOT NULL,
      target_key  TEXT NOT NULL,
      target_name TEXT,
      event       TEXT NOT NULL,
      severity    TEXT NOT NULL,
      message     TEXT NOT NULL,
      fired_at    TEXT NOT NULL,
      resolved_at TEXT,
      created_at  TEXT NOT NULL
    );

    CREATE INDEX IF NOT EXISTS idx_alert_history_created ON alert_history(created_at DESC);
    CREATE INDEX IF NOT EXISTS idx_alert_history_rule    ON alert_history(rule_id);
    CREATE INDEX IF NOT EXISTS idx_alert_history_event   ON alert_history(event);
  `);
  return _db;
}

function deserializeRule(row: RawAlertRule): AlertRule {
  return {
    id: row.id,
    name: row.name,
    enabled: row.enabled !== 0,
    target_type: row.target_type as TargetType,
    target_id: row.target_id,
    condition: JSON.parse(row.condition) as RuleCondition,
    severity: row.severity as Severity,
    cooldown_seconds: row.cooldown_seconds,
    channels: JSON.parse(row.channels) as string[],
    created_at: row.created_at,
    updated_at: row.updated_at,
  };
}

export function getRules(): AlertRule[] {
  const rows = getDb().prepare("SELECT * FROM alert_rules ORDER BY created_at ASC").all() as RawAlertRule[];
  return rows.map(deserializeRule);
}

export function getRule(id: string): AlertRule | undefined {
  const row = getDb().prepare("SELECT * FROM alert_rules WHERE id = ?").get(id) as RawAlertRule | undefined;
  return row ? deserializeRule(row) : undefined;
}

export function createRule(rule: Omit<AlertRule, "created_at" | "updated_at">): AlertRule {
  const now = new Date().toISOString();
  const db = getDb();
  db.prepare(`
    INSERT INTO alert_rules (id, name, enabled, target_type, target_id, condition, severity, cooldown_seconds, channels, created_at, updated_at)
    VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
  `).run(
    rule.id,
    rule.name,
    rule.enabled ? 1 : 0,
    rule.target_type,
    rule.target_id ?? null,
    JSON.stringify(rule.condition),
    rule.severity,
    rule.cooldown_seconds,
    JSON.stringify(rule.channels),
    now,
    now,
  );
  return getRule(rule.id)!;
}

export function updateRule(id: string, patch: Partial<Omit<AlertRule, "id" | "created_at" | "updated_at">>): AlertRule | undefined {
  const existing = getRule(id);
  if (!existing) return undefined;
  const now = new Date().toISOString();
  const merged = { ...existing, ...patch, updated_at: now };
  getDb().prepare(`
    UPDATE alert_rules
    SET name=?, enabled=?, target_type=?, target_id=?, condition=?, severity=?, cooldown_seconds=?, channels=?, updated_at=?
    WHERE id=?
  `).run(
    merged.name,
    merged.enabled ? 1 : 0,
    merged.target_type,
    merged.target_id ?? null,
    JSON.stringify(merged.condition),
    merged.severity,
    merged.cooldown_seconds,
    JSON.stringify(merged.channels),
    merged.updated_at,
    id,
  );
  return getRule(id);
}

export function deleteRule(id: string): boolean {
  const result = getDb().prepare("DELETE FROM alert_rules WHERE id = ?").run(id);
  return result.changes > 0;
}

export function ruleExists(id: string): boolean {
  const row = getDb().prepare("SELECT 1 FROM alert_rules WHERE id = ?").get(id);
  return row !== undefined;
}

export function getAlertState(ruleId: string, targetKey: string): AlertState | undefined {
  const row = getDb().prepare("SELECT * FROM alert_state WHERE rule_id = ? AND target_key = ?").get(ruleId, targetKey) as AlertState | undefined;
  return row;
}

export function upsertAlertState(state: AlertState): void {
  getDb().prepare(`
    INSERT INTO alert_state (rule_id, target_key, state, fired_at, last_notified_at)
    VALUES (?, ?, ?, ?, ?)
    ON CONFLICT(rule_id, target_key) DO UPDATE SET
      state = excluded.state,
      fired_at = excluded.fired_at,
      last_notified_at = excluded.last_notified_at
  `).run(state.rule_id, state.target_key, state.state, state.fired_at ?? null, state.last_notified_at ?? null);
}

export function clearAlertState(ruleId: string): void {
  getDb().prepare("DELETE FROM alert_state WHERE rule_id = ?").run(ruleId);
}

export function appendAlertHistory(row: AlertHistoryRow): void {
  getDb().prepare(`
    INSERT INTO alert_history (id, rule_id, rule_name, target_key, target_name, event, severity, message, fired_at, resolved_at, created_at)
    VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
  `).run(
    row.id,
    row.rule_id,
    row.rule_name,
    row.target_key,
    row.target_name ?? null,
    row.event,
    row.severity,
    row.message,
    row.fired_at,
    row.resolved_at ?? null,
    row.created_at,
  );
}

export function getAlertHistory(limit = 50, offset = 0): AlertHistoryRow[] {
  return getDb().prepare(
    "SELECT * FROM alert_history ORDER BY created_at DESC LIMIT ? OFFSET ?"
  ).all(limit, offset) as AlertHistoryRow[];
}

export function getAlertHistoryCount(): number {
  const row = getDb().prepare("SELECT COUNT(*) as c FROM alert_history").get() as { c: number };
  return row.c;
}

const DEFAULT_RULES: Omit<AlertRule, "created_at" | "updated_at">[] = [
  {
    id: "default-host-offline",
    name: "Host Unreachable",
    enabled: true,
    target_type: "host",
    target_id: null,
    condition: { kind: "state", states: ["offline"] },
    severity: "critical",
    cooldown_seconds: 300,
    channels: ["notifications", "imessage", "chronicle"],
  },
  {
    id: "default-host-disk-warning",
    name: "Host Disk > 85%",
    enabled: true,
    target_type: "host",
    target_id: null,
    condition: { kind: "threshold", metric: "disk_percent", operator: "gt", value: 85 },
    severity: "warning",
    cooldown_seconds: 3600,
    channels: ["notifications", "chronicle"],
  },
  {
    id: "default-host-disk-critical",
    name: "Host Disk > 95%",
    enabled: true,
    target_type: "host",
    target_id: null,
    condition: { kind: "threshold", metric: "disk_percent", operator: "gt", value: 95 },
    severity: "critical",
    cooldown_seconds: 1800,
    channels: ["notifications", "imessage", "chronicle"],
  },
  {
    id: "default-service-down",
    name: "Service Not Running",
    enabled: true,
    target_type: "service",
    target_id: null,
    condition: { kind: "state", states: ["errored", "stopped"] },
    severity: "critical",
    cooldown_seconds: 300,
    channels: ["notifications", "imessage", "chronicle"],
  },
  {
    id: "default-network-offline",
    name: "Network Connection Down",
    enabled: true,
    target_type: "network",
    target_id: null,
    condition: { kind: "state", states: ["offline"] },
    severity: "critical",
    cooldown_seconds: 300,
    channels: ["notifications", "imessage", "chronicle"],
  },
  {
    id: "default-site-down",
    name: "Site Down",
    enabled: true,
    target_type: "site",
    target_id: null,
    condition: { kind: "state", states: ["down"] },
    severity: "critical",
    cooldown_seconds: 300,
    channels: ["notifications", "chronicle"],
  },
];

export function seedDefaultRules(): void {
  const db = getDb();
  const now = new Date().toISOString();
  const insert = db.prepare(`
    INSERT OR IGNORE INTO alert_rules (id, name, enabled, target_type, target_id, condition, severity, cooldown_seconds, channels, created_at, updated_at)
    VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
  `);
  for (const rule of DEFAULT_RULES) {
    insert.run(
      rule.id,
      rule.name,
      rule.enabled ? 1 : 0,
      rule.target_type,
      rule.target_id ?? null,
      JSON.stringify(rule.condition),
      rule.severity,
      rule.cooldown_seconds,
      JSON.stringify(rule.channels),
      now,
      now,
    );
  }
}
