// Alert rules + local push delivery for the mobile app. Mirrors the shape
// used by the web app (src/lib/alert-rules.ts) so the two stay conceptually
// aligned: a global default plus optional per-symbol overrides for spread
// (bps) and tick latency (ms). Delivery is via expo-notifications, which
// fires OS-level local push whether the app is foregrounded, backgrounded,
// or killed (via the background-fetch task in ./backgroundAlerts.ts).
//
// Storage: AsyncStorage. The engine has zero backend dependencies — rules
// live entirely on-device.

import AsyncStorage from "@react-native-async-storage/async-storage";
import * as Notifications from "expo-notifications";
import { Platform } from "react-native";

const RULES_KEY = "delta.alertRules.v1";
const PUSH_KEY = "delta.alertPush.v1";
const LAST_FIRED_KEY = "delta.alertLastFired.v1";

export type AlertSeverity = "warn" | "critical";
export type AlertKind = "spread" | "latency";

export interface AlertThresholds {
  spreadEnabled: boolean;
  spreadWarnBps: number;
  spreadCritBps: number;
  latencyEnabled: boolean;
  latencyWarnMs: number;
  latencyCritMs: number;
}

export interface AlertRulesState {
  global: AlertThresholds;
  overrides: Record<string, AlertThresholds>;
  cooldownMs: number;
  enabled: boolean;
}

export const DEFAULT_THRESHOLDS: AlertThresholds = {
  spreadEnabled: true,
  spreadWarnBps: 20,
  spreadCritBps: 50,
  latencyEnabled: true,
  latencyWarnMs: 3_000,
  latencyCritMs: 10_000,
};

export const DEFAULT_RULES: AlertRulesState = {
  global: DEFAULT_THRESHOLDS,
  overrides: {},
  cooldownMs: 60_000,
  enabled: true,
};

// -------- storage --------

export async function readRules(): Promise<AlertRulesState> {
  try {
    const raw = await AsyncStorage.getItem(RULES_KEY);
    if (!raw) return DEFAULT_RULES;
    const parsed = JSON.parse(raw) as Partial<AlertRulesState>;
    return {
      global: { ...DEFAULT_THRESHOLDS, ...(parsed.global ?? {}) },
      overrides: parsed.overrides ?? {},
      cooldownMs: parsed.cooldownMs ?? DEFAULT_RULES.cooldownMs,
      enabled: parsed.enabled ?? true,
    };
  } catch {
    return DEFAULT_RULES;
  }
}

export async function writeRules(rules: AlertRulesState) {
  await AsyncStorage.setItem(RULES_KEY, JSON.stringify(rules));
}

export async function readPushEnabled(): Promise<boolean> {
  const raw = await AsyncStorage.getItem(PUSH_KEY);
  return raw === "1";
}

export async function writePushEnabled(v: boolean) {
  await AsyncStorage.setItem(PUSH_KEY, v ? "1" : "0");
}

// -------- permissions + notification channel --------

/**
 * Ensures notification permission is granted and (on Android) creates a
 * high-importance channel for alerts. Returns true if push is usable.
 */
export async function ensureNotificationPermission(): Promise<boolean> {
  const cur = await Notifications.getPermissionsAsync();
  let status = cur.status;
  if (status !== "granted") {
    const req = await Notifications.requestPermissionsAsync({
      ios: {
        allowAlert: true,
        allowBadge: true,
        allowSound: true,
      },
    });
    status = req.status;
  }
  if (status !== "granted") return false;

  if (Platform.OS === "android") {
    await Notifications.setNotificationChannelAsync("alerts-critical", {
      name: "Critical alerts",
      importance: Notifications.AndroidImportance.MAX,
      vibrationPattern: [0, 250, 250, 250],
      lightColor: "#ef4444",
      sound: "default",
    });
    await Notifications.setNotificationChannelAsync("alerts-warn", {
      name: "Warning alerts",
      importance: Notifications.AndroidImportance.HIGH,
      lightColor: "#f59e0b",
      sound: "default",
    });
  }
  return true;
}

// -------- evaluation --------

export interface TickerLike {
  symbol: string;
  spread_bps?: number | null;
  lastUpdate?: number | null; // ms epoch
}

type FiredMap = Record<string, { at: number; severity: AlertSeverity }>;

async function readFired(): Promise<FiredMap> {
  try {
    const raw = await AsyncStorage.getItem(LAST_FIRED_KEY);
    return raw ? (JSON.parse(raw) as FiredMap) : {};
  } catch {
    return {};
  }
}
async function writeFired(m: FiredMap) {
  await AsyncStorage.setItem(LAST_FIRED_KEY, JSON.stringify(m));
}

function spreadSeverity(bps: number | null | undefined, t: AlertThresholds): AlertSeverity | null {
  if (!t.spreadEnabled || bps == null || !Number.isFinite(bps)) return null;
  if (bps >= t.spreadCritBps) return "critical";
  if (bps >= t.spreadWarnBps) return "warn";
  return null;
}
function latencySeverity(ageMs: number, t: AlertThresholds): AlertSeverity | null {
  if (!t.latencyEnabled) return null;
  if (ageMs >= t.latencyCritMs) return "critical";
  if (ageMs >= t.latencyWarnMs) return "warn";
  return null;
}

function fmtAge(ms: number) {
  if (ms < 1000) return `${ms}ms`;
  const s = ms / 1000;
  if (s < 60) return `${s.toFixed(s < 10 ? 1 : 0)}s`;
  return `${Math.floor(s / 60)}m`;
}

/**
 * Runs the rules once against the given ticker snapshot and fires local
 * notifications for any breaches. Persists a per-(symbol,kind) last-fired
 * map so cooldowns work across foreground / background boundaries.
 * Returns the number of notifications scheduled.
 */
export async function evaluateAndNotify(tickers: TickerLike[]): Promise<number> {
  const [rules, fired, pushOn] = await Promise.all([
    readRules(),
    readFired(),
    readPushEnabled(),
  ]);
  if (!rules.enabled || !pushOn) return 0;

  const now = Date.now();
  let scheduled = 0;
  const nextFired: FiredMap = { ...fired };

  for (const t of tickers) {
    const th = rules.overrides[t.symbol] ?? rules.global;
    const checks: Array<{ kind: AlertKind; sev: AlertSeverity; detail: string }> = [];

    const sSev = spreadSeverity(t.spread_bps, th);
    if (sSev) {
      checks.push({
        kind: "spread",
        sev: sSev,
        detail: `spread ${(t.spread_bps ?? 0).toFixed(1)} bps (≥ ${sSev === "critical" ? th.spreadCritBps : th.spreadWarnBps})`,
      });
    }
    const age = now - (t.lastUpdate ?? now);
    const lSev = latencySeverity(age, th);
    if (lSev) {
      checks.push({
        kind: "latency",
        sev: lSev,
        detail: `tick ${fmtAge(age)} stale (≥ ${fmtAge(lSev === "critical" ? th.latencyCritMs : th.latencyWarnMs)})`,
      });
    }

    for (const c of checks) {
      const key = `${t.symbol}:${c.kind}`;
      const prev = nextFired[key];
      const escalated = !prev || (prev.severity === "warn" && c.sev === "critical");
      const cooled = !prev || now - prev.at >= rules.cooldownMs;
      if (!escalated && !cooled) continue;
      nextFired[key] = { at: now, severity: c.sev };
      await Notifications.scheduleNotificationAsync({
        content: {
          title: `${t.symbol} · ${c.kind === "spread" ? "Spread alert" : "Latency alert"}`,
          body: c.detail,
          sound: "default",
          priority:
            c.sev === "critical"
              ? Notifications.AndroidNotificationPriority.MAX
              : Notifications.AndroidNotificationPriority.HIGH,
          data: { symbol: t.symbol, kind: c.kind, severity: c.sev },
        },
        trigger:
          Platform.OS === "android"
            ? { channelId: c.sev === "critical" ? "alerts-critical" : "alerts-warn" }
            : null,
      });
      scheduled++;
    }

    // Recovery clears cooldown so the next breach fires immediately.
    if (!sSev) delete nextFired[`${t.symbol}:spread`];
    if (!lSev) delete nextFired[`${t.symbol}:latency`];
  }

  await writeFired(nextFired);
  return scheduled;
}

/** Fires a small confirmation push. Used by the "Test" button in Settings. */
export async function fireTestNotification() {
  await Notifications.scheduleNotificationAsync({
    content: {
      title: "Delta Terminal · Alerts armed",
      body: "You'll be notified when spread or tick latency crosses your thresholds.",
      sound: "default",
      data: { test: true },
    },
    trigger:
      Platform.OS === "android"
        ? { channelId: "alerts-warn" }
        : null,
  });
}
