import { eq, and, gte, desc } from "drizzle-orm";
import { alerts, alertRules } from "../../db/schema";
import type { DrizzleDB } from "../../db/config/migrations";
import type { NewAlert, Alert } from "../../db/schema";
import { checkKPIAgainstThreshold, type KPIKey } from "./kpi-service";

export interface SlackConfig {
  webhookUrl: string;
  defaultChannel?: string;
}

export interface AlertResult {
  triggered: boolean;
  alert?: Alert;
  ruleName: string;
  severity: "low" | "medium" | "high" | "critical";
}

export async function evaluateAlertRules(
  db: DrizzleDB,
  kpiKey: KPIKey,
  currentValue: number
): Promise<AlertResult[]> {
  const activeRules = await db
    .select()
    .from(alertRules)
    .where(and(
      eq(alertRules.kpiKey, kpiKey),
      eq(alertRules.isActive, true)
    ));

  const results: AlertResult[] = [];

  for (const rule of activeRules) {
    const triggered = isRuleTriggered(rule, currentValue);

    if (!triggered) {
      results.push({ triggered: false, ruleName: rule.name, severity: rule.severity });
      continue;
    }

    const cooldownMs = rule.cooldownMinutes * 60 * 1000;
    const cooldownCutoff = new Date(Date.now() - cooldownMs);

    const recentAlert = await db
      .select({ id: alerts.id })
      .from(alerts)
      .where(and(
        eq(alerts.ruleId, rule.id),
        gte(alerts.createdAt, cooldownCutoff)
      ))
      .orderBy(desc(alerts.createdAt))
      .limit(1);

    if (recentAlert.length > 0) {
      results.push({ triggered: false, ruleName: rule.name, severity: rule.severity });
      continue;
    }

    const severity = mapSeverity(rule, currentValue, kpiKey);
    const message = formatAlertMessage(rule, currentValue, kpiKey);

    const newAlert: NewAlert = {
      ruleId: rule.id,
      kpiKey,
      kpiValue: currentValue,
      threshold: rule.threshold,
      severity,
      message,
      wasSent: false,
    };

    const result = await db.insert(alerts).values(newAlert).returning();
    const alert = result[0];

    results.push({ triggered: true, alert, ruleName: rule.name, severity });
  }

  return results;
}

function isRuleTriggered(rule: typeof alertRules.$inferSelect, value: number): boolean {
  switch (rule.condition) {
    case "above":
      return value > rule.threshold;
    case "below":
      return value < rule.threshold;
    case "equals":
      return value === rule.threshold;
    case "increasing":
      return value > rule.threshold;
    case "decreasing":
      return value < rule.threshold;
    default:
      return false;
  }
}

function mapSeverity(
  rule: typeof alertRules.$inferSelect,
  _value: number,
  kpiKey: KPIKey
): "low" | "medium" | "high" | "critical" {
  const { severity: kpiSeverity } = checkKPIAgainstThreshold(kpiKey, _value);

  if (kpiSeverity === "critical") return "critical";
  if (kpiSeverity === "warning") return rule.severity === "critical" ? "high" : "medium";
  return rule.severity;
}

function escapeSlackMarkdown(text: string): string {
  return text
    .replace(/&/g, '&amp;')
    .replace(/</g, '&lt;')
    .replace(/>/g, '&gt;')
    .replace(/\*/g, '\\*')
    .replace(/_/g, '\\_')
    .replace(/~/g, '\\~')
    .replace(/`/g, '\\`')
    .replace(/\(/g, '\\(');
}

export function formatAlertMessage(
  rule: typeof alertRules.$inferSelect,
  value: number,
  kpiKey: KPIKey
): string {
  const formattedValue = Number.isInteger(value) ? value.toString() : value.toFixed(2);
  const formattedThreshold = Number.isInteger(rule.threshold)
    ? rule.threshold.toString()
    : rule.threshold.toFixed(2);

  const safeName = escapeSlackMarkdown(rule.name);

  return `${safeName}: ${kpiKey} is ${formattedValue} (${rule.condition} ${formattedThreshold})`;
}

export async function sendSlackAlert(
  config: SlackConfig,
  alert: Alert,
  overrideChannel?: string
): Promise<boolean> {
  const channel = overrideChannel || config.defaultChannel || "#alerts";
  const color = alertSeverityToSlackColor(alert.severity);

  const blocks = [
    {
      type: "header",
      text: {
        type: "plain_text",
        text: `🚨 KPI Alert: ${alert.kpiKey}`,
        emoji: true,
      },
    },
    {
      type: "section",
      fields: [
        {
          type: "mrkdwn",
          text: `*Severity:*\n${alert.severity.toUpperCase()}`,
        },
        {
          type: "mrkdwn",
          text: `*Current Value:*\n${alert.kpiValue.toFixed(2)}`,
        },
        {
          type: "mrkdwn",
          text: `*Threshold:*\n${alert.threshold.toFixed(2)}`,
        },
        {
          type: "mrkdwn",
          text: `*Time:*\n${new Date(alert.createdAt.getTime()).toISOString()}`,
        },
      ],
    },
    {
      type: "section",
      text: {
        type: "mrkdwn",
        text: alert.message,
      },
    },
  ];

  const payload = {
    channel,
    blocks,
    username: "FrenoCorp Alerts",
    icon_emoji: ":warning:",
  };

  try {
    const response = await fetch(config.webhookUrl, {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify(payload),
    });

    if (!response.ok) {
      console.error(`Slack webhook error: ${response.status} ${response.statusText}`);
      return false;
    }

    await markAlertSent(alert.id);
    return true;
  } catch (error) {
    console.error("Failed to send Slack alert:", error);
    return false;
  }
}

async function markAlertSent(alertId: number): Promise<void> {
  try {
    const { db } = await import("../../db/config/migrations");
    await db
      .update(alerts)
      .set({ wasSent: true, sentAt: new Date() })
      .where(eq(alerts.id, alertId));
  } catch {
    console.error("Failed to mark alert as sent:", alertId);
  }
}

function alertSeverityToSlackColor(
  severity: "low" | "medium" | "high" | "critical"
): string {
  switch (severity) {
    case "critical":
      return "#FF0000";
    case "high":
      return "#FFA500";
    case "medium":
      return "#FFFF00";
    case "low":
      return "#00FF00";
    default:
      return "#808080";
  }
}

export async function acknowledgeAlert(
  db: DrizzleDB,
  alertId: number,
  acknowledgedBy: number
): Promise<Alert | null> {
  const result = await db
    .update(alerts)
    .set({ acknowledgedBy, acknowledgedAt: new Date() })
    .where(eq(alerts.id, alertId))
    .returning();

  return result[0] ?? null;
}

export async function getUnsentAlerts(db: DrizzleDB): Promise<Alert[]> {
  return await db
    .select()
    .from(alerts)
    .where(eq(alerts.wasSent, false))
    .orderBy(alerts.createdAt);
}
