/**
 * AI Interaction Logger
 * ─────────────────────
 * Writes to AiInteraction with basic guardrail flag scoring. Called
 * from each AI feature's route (ai-strategist, content-brief, schema
 * generator, etc.). AiUsageLog stays the source of truth for token +
 * cost accounting; this log is the audit surface powering
 * /admin/ai-monitor.
 *
 * Guardrail flags are purely heuristic regex matches — cheap,
 * deterministic, and good enough to catch the most common footguns
 * (prompt injection attempts, PII leaks, medical/legal advice,
 * hallucinated URLs). A proper classifier lands later; the shape of
 * the stored record doesn't change when that happens, so the admin
 * UI keeps working.
 *
 * Writes are fire-and-forget (.catch-swallowed) so guardrail errors
 * can never break a user's AI request.
 */

import { prisma } from "@/lib/prisma";

export type AiFeature =
  | "ai_strategist"
  | "content_brief"
  | "llmo_brief"
  | "schema_gen"
  | "industry_intel"
  | "competitor_discovery"
  | "content_recommendations"
  | "report_studio"
  | "email_report"
  | "topic_map"
  | "other";

export type GuardrailSeverity = "none" | "warning" | "critical" | "blocked";

export interface LogAiInteractionInput {
  userId?: string | null;
  brandId?: string | null;
  feature: AiFeature;
  userPrompt: string;
  systemPrompt?: string | null;
  aiResponse: string;
  model: string;
  tokensUsed?: number | null;
  costEstimate?: number | null;
  /** Brand domain — used by URL hallucination detection so an AI
   *  response citing urls outside the brand's own domain gets
   *  flagged as a potential hallucination. Optional. */
  brandDomain?: string | null;
}

// ─── Regex guardrails ────────────────────────────────────────────
//
// Keep these conservative. Over-flagging erodes trust in the admin
// dashboard more than under-flagging does; a missed edge case is
// still catchable via a human review pass on the AiInteraction table.

const PROMPT_INJECTION_PATTERNS = [
  /ignore (?:all |the )?previous (?:instructions|prompts)/i,
  /you are now (?:a|an) (?!helpful)/i,          // ignore "you are now a helpful AI"
  /system prompt:/i,
  /disregard (?:all |your )?previous/i,
  /act as (?:if you are )?(?:no longer|not)/i,
];

const PII_PATTERNS = [
  // US-shaped SSNs (3-2-4). Conservative: requires dash so we don't
  // flag every 9-digit order number.
  /\b\d{3}-\d{2}-\d{4}\b/,
  // Email addresses. Captures standard RFC-5322-ish shapes.
  /\b[a-z0-9._%+-]+@[a-z0-9.-]+\.[a-z]{2,}\b/i,
  // US phone numbers (10 digits with various separators).
  /\b(?:\+?1[\s.-]?)?\(?\d{3}\)?[\s.-]?\d{3}[\s.-]?\d{4}\b/,
];

const MEDICAL_ADVICE_PATTERNS = [
  /\b(?:take|prescribe|dose|dosage) \d+ ?(?:mg|mcg|ml|tablets?|pills?)\b/i,
  /\b(?:diagnose|diagnosis of|treatment for) your\b/i,
];

const LEGAL_ADVICE_PATTERNS = [
  /\byou (?:have|are entitled to|should) sue\b/i,
  /\bthis (?:is|constitutes) (?:a |an )?(?:crime|felony|misdemeanor)\b/i,
];

const ROI_GUARANTEE_PATTERNS = [
  /\bguarantee[ds]? (?:\d+x|\d+%|\$\d+)/i,
  /\b(?:we |I )guarantee\b/i,
];

const MODEL_REFUSAL_PATTERNS = [
  /^I (?:cannot|can't|am unable to|won't) /i,
  /^I'm (?:sorry|unable to|not able to) /i,
  /^As an AI /i,
];

function anyMatch(text: string, patterns: RegExp[]): boolean {
  return patterns.some((p) => p.test(text));
}

export interface GuardrailResult {
  flags: string[];
  severity: GuardrailSeverity;
}

/** Run guardrail heuristics. `blocked` escalates the record's
 *  severity so the admin UI can separate "flagged but still returned"
 *  from "refused before returning". The caller decides whether to
 *  actually block the response based on the severity. */
export function runGuardrails(input: LogAiInteractionInput): GuardrailResult {
  const flags: string[] = [];

  // User-prompt hostility checks.
  if (anyMatch(input.userPrompt, PROMPT_INJECTION_PATTERNS)) flags.push("prompt_injection");

  // Response content checks.
  if (anyMatch(input.aiResponse, PII_PATTERNS)) flags.push("pii_detected");
  if (anyMatch(input.aiResponse, MEDICAL_ADVICE_PATTERNS)) flags.push("medical_advice");
  if (anyMatch(input.aiResponse, LEGAL_ADVICE_PATTERNS)) flags.push("legal_advice");
  if (anyMatch(input.aiResponse, ROI_GUARANTEE_PATTERNS)) flags.push("roi_guarantee");

  // Quality checks.
  if (input.aiResponse.trim().length < 50) flags.push("suspiciously_short");
  if (anyMatch(input.aiResponse.trim().slice(0, 200), MODEL_REFUSAL_PATTERNS)) flags.push("model_refusal");

  // Hallucinated URL detection — any http(s) URL in the response
  // whose host doesn't match the brand's own domain gets flagged.
  // Skip when the brand domain isn't known.
  if (input.brandDomain) {
    const normalizedBrand = input.brandDomain.replace(/^https?:\/\//, "").replace(/\/.*$/, "").toLowerCase();
    const urlMatches = Array.from(input.aiResponse.matchAll(/https?:\/\/([a-z0-9.-]+)/gi));
    const hasForeignUrl = urlMatches.some((m) => {
      const host = m[1].toLowerCase();
      return host !== normalizedBrand
        && !host.endsWith("." + normalizedBrand)
        && !isWellKnownHost(host);
    });
    if (hasForeignUrl) flags.push("foreign_url");
  }

  // Severity: anything in the "critical" set wins; anything in the
  // "blocked" set escalates to blocked. Otherwise severity = warning
  // if any flag fired, else none.
  const CRITICAL = new Set(["pii_detected", "prompt_injection", "medical_advice", "legal_advice"]);
  const severity: GuardrailSeverity = flags.length === 0 ? "none"
    : flags.some((f) => CRITICAL.has(f)) ? "critical"
    : "warning";

  return { flags, severity };
}

const WELL_KNOWN_HOSTS = new Set([
  "google.com", "www.google.com", "support.google.com", "developers.google.com",
  "schema.org", "www.w3.org", "developer.mozilla.org",
  "github.com", "docs.anthropic.com", "openai.com",
]);
function isWellKnownHost(host: string): boolean {
  if (WELL_KNOWN_HOSTS.has(host)) return true;
  // search.google.com, www.google.com/webmasters, etc.
  if (host.endsWith(".google.com") || host.endsWith(".schema.org")) return true;
  return false;
}

/** Fire-and-forget log write. Truncates large fields to keep Postgres
 *  row size manageable — 4KB for prompts, 16KB for responses matches
 *  the Prisma schema comments. */
export function logAiInteraction(input: LogAiInteractionInput): void {
  void writeLog(input).catch((err) => {
    console.warn("[ai-interaction-log] write failed", err instanceof Error ? err.message : err);
  });
}

async function writeLog(input: LogAiInteractionInput): Promise<void> {
  const { flags, severity } = runGuardrails(input);
  await prisma.aiInteraction.create({
    data: {
      userId: input.userId ?? null,
      brandId: input.brandId ?? null,
      feature: input.feature,
      userPrompt: input.userPrompt.slice(0, 4 * 1024),
      systemPrompt: input.systemPrompt ? input.systemPrompt.slice(0, 8 * 1024) : null,
      aiResponse: input.aiResponse.slice(0, 16 * 1024),
      model: input.model,
      tokensUsed: input.tokensUsed ?? null,
      costEstimate: input.costEstimate ?? null,
      guardrailFlags: flags,
      flagSeverity: severity,
    },
  });
}
