import {
  getRules,
  appendAlertHistory,
  type AlertRule,
  type AlertHistoryRow,
  type ThresholdCondition,
  type StateCondition,
} from "../db.js";
import { getHosts, getServices, getNetworkStatuses, getSites } from "../cache.js";
import { evaluateTransition } from "./deduplicator.js";
import { dispatch } from "./channels.js";
import type { HostStatus, ServiceStatus, NetworkStatus, SiteStatus } from "../types.js";

function getHostMetric(host: HostStatus, metric: ThresholdCondition["metric"]): number | null {
  switch (metric) {
    case "disk_percent":
      return host.disk_total_bytes > 0
        ? (host.disk_used_bytes / host.disk_total_bytes) * 100
        : null;
    case "memory_percent":
      return host.memory_total_bytes > 0
        ? (host.memory_used_bytes / host.memory_total_bytes) * 100
        : null;
    case "cpu_percent":
      return host.cpu_percent;
    default:
      return null;
  }
}

function getNetworkMetric(net: NetworkStatus, metric: ThresholdCondition["metric"]): number | null {
  if (metric === "latency_ms") return net.latency_ms;
  return null;
}

function getSiteMetric(site: SiteStatus, metric: ThresholdCondition["metric"]): number | null {
  switch (metric) {
    case "response_time_ms": return site.response_time_ms;
    case "ssl_days_remaining": return site.ssl_days_remaining;
    default: return null;
  }
}

function compareThreshold(actual: number, cond: ThresholdCondition): boolean {
  switch (cond.operator) {
    case "gt":  return actual > cond.value;
    case "gte": return actual >= cond.value;
    case "lt":  return actual < cond.value;
    case "lte": return actual <= cond.value;
  }
}

function isBreachingHost(host: HostStatus, rule: AlertRule): { breaching: boolean; message: string } {
  const cond = rule.condition;
  if (cond.kind === "state") {
    const breaching = (cond as StateCondition).states.includes(host.status);
    return { breaching, message: breaching ? `Host is ${host.status}` : "" };
  }
  const tc = cond as ThresholdCondition;
  const actual = getHostMetric(host, tc.metric);
  if (actual === null) return { breaching: false, message: "" };
  const breaching = compareThreshold(actual, tc);
  return { breaching, message: breaching ? `${tc.metric}: ${actual.toFixed(1)} ${tc.operator} ${tc.value}` : "" };
}

function isBreachingService(svc: ServiceStatus, rule: AlertRule): { breaching: boolean; message: string } {
  const cond = rule.condition;
  if (cond.kind !== "state") return { breaching: false, message: "" };
  const breaching = (cond as StateCondition).states.includes(svc.status);
  return { breaching, message: breaching ? `Service is ${svc.status}` : "" };
}

function isBreachingNetwork(net: NetworkStatus, rule: AlertRule): { breaching: boolean; message: string } {
  const cond = rule.condition;
  if (cond.kind === "state") {
    const breaching = (cond as StateCondition).states.includes(net.status);
    return { breaching, message: breaching ? `Connection is ${net.status}` : "" };
  }
  const tc = cond as ThresholdCondition;
  const actual = getNetworkMetric(net, tc.metric);
  if (actual === null) return { breaching: false, message: "" };
  const breaching = compareThreshold(actual, tc);
  return { breaching, message: breaching ? `${tc.metric}: ${actual.toFixed(1)} ${tc.operator} ${tc.value}` : "" };
}

function isBreachingSite(site: SiteStatus, rule: AlertRule): { breaching: boolean; message: string } {
  const cond = rule.condition;
  if (cond.kind === "state") {
    const breaching = (cond as StateCondition).states.includes(site.status);
    return { breaching, message: breaching ? `Site is ${site.status}` : "" };
  }
  const tc = cond as ThresholdCondition;
  const actual = getSiteMetric(site, tc.metric);
  if (actual === null) return { breaching: false, message: "" };
  const breaching = compareThreshold(actual, tc);
  return { breaching, message: breaching ? `${tc.metric}: ${actual.toFixed(1)} ${tc.operator} ${tc.value}` : "" };
}

async function evaluateTarget(
  rule: AlertRule,
  targetKey: string,
  targetName: string,
  isBreaching: boolean,
  message: string,
): Promise<void> {
  const action = evaluateTransition(rule.id, targetKey, isBreaching, rule.cooldown_seconds);
  if (!action) return;

  const now = new Date().toISOString();
  const historyRow: AlertHistoryRow = {
    id: crypto.randomUUID(),
    rule_id: rule.id,
    rule_name: rule.name,
    target_key: targetKey,
    target_name: targetName,
    event: action === "fire" ? "firing" : "resolved",
    severity: rule.severity,
    message: action === "fire" ? message : `${rule.name} resolved for ${targetName}`,
    fired_at: action === "fire" ? now : now,
    resolved_at: action === "resolve" ? now : null,
    created_at: now,
  };

  appendAlertHistory(historyRow);

  await dispatch({
    rule,
    historyRow,
    targetName,
    message: historyRow.message,
    isRecovery: action === "resolve",
  });
}

export async function runEvaluator(): Promise<void> {
  const rules = getRules().filter((r) => r.enabled);
  if (rules.length === 0) return;

  const hosts    = getHosts();
  const services = getServices();
  const network  = getNetworkStatuses();
  const sites    = getSites();

  const tasks: Promise<void>[] = [];

  for (const rule of rules) {
    switch (rule.target_type) {
      case "host": {
        const targets = rule.target_id
          ? hosts.filter((h) => h.id === rule.target_id)
          : hosts;
        for (const h of targets) {
          const { breaching, message } = isBreachingHost(h, rule);
          tasks.push(evaluateTarget(rule, `host:${h.id}`, h.name, breaching, message));
        }
        break;
      }
      case "service": {
        const targets = rule.target_id
          ? services.filter((s) => s.id === rule.target_id)
          : services;
        for (const s of targets) {
          const { breaching, message } = isBreachingService(s, rule);
          tasks.push(evaluateTarget(rule, `service:${s.id}`, s.name, breaching, message));
        }
        break;
      }
      case "network": {
        const targets = rule.target_id
          ? network.filter((n) => n.id === rule.target_id)
          : network;
        for (const n of targets) {
          const { breaching, message } = isBreachingNetwork(n, rule);
          tasks.push(evaluateTarget(rule, `network:${n.id}`, n.name, breaching, message));
        }
        break;
      }
      case "site": {
        const targets = rule.target_id
          ? sites.filter((s) => s.id === rule.target_id)
          : sites;
        for (const s of targets) {
          const { breaching, message } = isBreachingSite(s, rule);
          tasks.push(evaluateTarget(rule, `site:${s.id}`, s.name, breaching, message));
        }
        break;
      }
    }
  }

  await Promise.allSettled(tasks);
}
