import { createHash } from "node:crypto";

interface ScanResult {
  source: "hibp" | "securityTrails" | "censys" | "shodan" | "darkWebForum";
  dataType: "email" | "phoneNumber" | "ssn" | "address" | "domain";
  identifier: string;
  identifierHash: string;
  metadata: Record<string, unknown>;
  detectedAt: Date;
  severity: "info" | "warning" | "critical";
}

interface CircuitState {
  failures: number;
  lastFailure: number;
  isOpen: boolean;
}

const circuits = new Map<string, CircuitState>();
const THRESHOLD = 5;
const RESET_MS = 60_000;

function isCircuitOpen(name: string): boolean {
  const state = circuits.get(name);
  if (!state) return false;
  if (!state.isOpen) return false;
  if (Date.now() - state.lastFailure > RESET_MS) {
    circuits.delete(name);
    return false;
  }
  return true;
}

function recordFailure(name: string): void {
  const state = circuits.get(name) ?? { failures: 0, lastFailure: 0, isOpen: false };
  state.failures++;
  state.lastFailure = Date.now();
  if (state.failures >= THRESHOLD) {
    state.isOpen = true;
  }
  circuits.set(name, state);
}

function recordSuccess(name: string): void {
  circuits.delete(name);
}

async function fetchWithCircuit(name: string, url: string, headers: Record<string, string>): Promise<Response | null> {
  if (isCircuitOpen(name)) {
    console.warn(`[darkwatch] Circuit open for ${name}, skipping`);
    return null;
  }
  try {
    const res = await fetch(url, { headers, signal: AbortSignal.timeout(10_000) });
    if (!res.ok) {
      recordFailure(name);
      console.warn(`[darkwatch] ${name} returned ${res.status}`);
      return null;
    }
    recordSuccess(name);
    return res;
  } catch (err) {
    recordFailure(name);
    console.error(`[darkwatch] ${name} error:`, err);
    return null;
  }
}

function hashValue(value: string): string {
  return createHash("sha256").update(value.toLowerCase().trim()).digest("hex");
}

export async function scanHIBP(email: string): Promise<ScanResult[]> {
  const apiKey = process.env.HIBP_API_KEY;
  if (!apiKey) {
    console.warn("[darkwatch] HIBP_API_KEY not set, skipping HIBP scan");
    return [];
  }
  const res = await fetchWithCircuit(
    "hibp",
    `https://haveibeenpwned.com/api/v3/breachedaccount/${encodeURIComponent(email)}?truncateResponse=false`,
    { "hibp-api-key": apiKey, "user-agent": "ShieldAI-DarkWatch" },
  );
  if (!res) return [];
  const breaches = await res.json() as Array<{ Name: string; BreachDate: string; DataClasses: string[]; Description: string }>;
  return breaches.map((b) => ({
    source: "hibp" as const,
    dataType: "email" as const,
    identifier: email,
    identifierHash: hashValue(email),
    metadata: { breachName: b.Name, breachDate: b.BreachDate, dataClasses: b.DataClasses, description: b.Description },
    detectedAt: new Date(b.BreachDate),
    severity: "critical" as const,
  }));
}

export async function scanSecurityTrails(identifier: string): Promise<ScanResult[]> {
  const apiKey = process.env.SECURITYTRAILS_API_KEY;
  if (!apiKey) {
    console.warn("[darkwatch] SECURITYTRAILS_API_KEY not set, skipping");
    return [];
  }
  const domain = identifier.includes("@") ? identifier.split("@")[1] : identifier;
  const res = await fetchWithCircuit(
    "securitytrails",
    `https://api.securitytrails.com/v1/domain/${encodeURIComponent(domain)}/subdomains`,
    { APIKEY: apiKey },
  );
  if (!res) return [];
  const data = await res.json() as { subdomains: string[] };
  return (data.subdomains ?? []).slice(0, 20).map((sub) => ({
    source: "securityTrails" as const,
    dataType: "domain" as const,
    identifier: `${sub}.${domain}`,
    identifierHash: hashValue(`${sub}.${domain}`),
    metadata: { subdomain: sub, domain },
    detectedAt: new Date(),
    severity: "info" as const,
  }));
}

export async function scanCensys(query: string): Promise<ScanResult[]> {
  const apiKey = process.env.CENSYS_API_KEY;
  if (!apiKey) {
    console.warn("[darkwatch] CENSYS_API_KEY not set, skipping");
    return [];
  }
  const res = await fetchWithCircuit(
    "censys",
    `https://search.censys.io/api/v2/hosts/search?q=${encodeURIComponent(query)}&per_page=10`,
    { Authorization: `Bearer ${apiKey}` },
  );
  if (!res) return [];
  const data = await res.json() as { result?: { hits?: Array<{ ip: string; services?: Array<{ service_name: string; port: number }> }> } };
  const hits = data.result?.hits ?? [];
  return hits.map((h) => ({
    source: "censys" as const,
    dataType: "domain" as const,
    identifier: h.ip,
    identifierHash: hashValue(h.ip),
    metadata: { ip: h.ip, services: h.services },
    detectedAt: new Date(),
    severity: "warning" as const,
  }));
}

export async function scanShodan(query: string): Promise<ScanResult[]> {
  const apiKey = process.env.SHODAN_API_KEY;
  if (!apiKey) {
    console.warn("[darkwatch] SHODAN_API_KEY not set, skipping");
    return [];
  }
  const res = await fetchWithCircuit(
    "shodan",
    `https://api.shodan.io/shodan/host/search?key=${apiKey}&query=${encodeURIComponent(query)}&limit=10`,
    {},
  );
  if (!res) return [];
  const data = await res.json() as { matches?: Array<{ ip_str: string; port: number; org?: string; hostnames?: string[] }> };
  const matches = data.matches ?? [];
  return matches.map((m) => ({
    source: "shodan" as const,
    dataType: "domain" as const,
    identifier: m.ip_str,
    identifierHash: hashValue(m.ip_str),
    metadata: { ip: m.ip_str, port: m.port, org: m.org, hostnames: m.hostnames },
    detectedAt: new Date(),
    severity: "warning" as const,
  }));
}

export async function scanForums(identifier: string): Promise<ScanResult[]> {
  const forumEnabled = process.env.DARKWEB_FORUM_ENABLED;
  if (!forumEnabled || forumEnabled !== "true") {
    return [];
  }
  return [{
    source: "darkWebForum" as const,
    dataType: (identifier.includes("@") ? "email" : "domain") as "email" | "domain",
    identifier,
    identifierHash: hashValue(identifier),
    metadata: { note: "Forum scraping placeholder", identifier },
    detectedAt: new Date(),
    severity: "warning" as const,
  }];
}

export type { ScanResult };
