import { randomUUID } from "node:crypto";
import { getDbPool } from "@/db";
import { getCapturePolicy } from "@/lib/capture-policy";

type DbRow = Record<string, unknown>;
type Rule = { id: string; code: string; name: string; points: number | string; action: string; condition_json: string | Record<string, unknown>; };
type Condition = { type?: string; values?: string[]; threshold?: number };

export type RuleEvaluation = { score: number; decision: "approved" | "approved_with_notes" | "human_review" | "retriage" | "rejected"; rulesVersion: string; triggered: { code: string; name: string; points: number; action: string }[] };
type TriggeredRule = RuleEvaluation["triggered"][number] & { source: "ai_analysis" | "human_assessment"; evidence?: Record<string, unknown> };

function json(value: unknown): Record<string, unknown> {
  if (typeof value === "object" && value) return value as Record<string, unknown>;
  try { return JSON.parse(String(value || "{}")) as Record<string, unknown>; } catch { return {}; }
}

function photoText(photo: DbRow) {
  const findings = json(photo.findings).items;
  const findingText = Array.isArray(findings) ? findings.join(" ") : String(photo.findings || "");
  return `${photo.summary || ""} ${findingText}`.toLocaleLowerCase("pt-BR");
}

function matches(rule: Rule, photos: DbRow[]) {
  const condition = json(rule.condition_json) as Condition;
  const values = (condition.values || []).map((value) => value.toLocaleLowerCase("pt-BR"));
  if (condition.type === "requires_human") return photos.some((photo) => Boolean(photo.requires_human));
  if (condition.type === "min_confidence") return photos.some((photo) => photo.confidence !== null && photo.confidence !== undefined && Number(photo.confidence) < Number(condition.threshold ?? 0));
  if (condition.type === "ai_status") return photos.some((photo) => values.includes(String(photo.analysis_status || "").toLowerCase()));
  if (condition.type === "finding_contains") return photos.some((photo) => values.some((value) => photoText(photo).includes(value)));
  return false;
}

function manualMatches(rule: Rule, assessments: DbRow[]) {
  const condition = json(rule.condition_json) as Condition;
  if (condition.type !== "manual_assessment") return [];
  const accepted = (condition.values || []).map((value) => value.toLocaleLowerCase("pt-BR"));
  return assessments.filter((assessment) => accepted.includes(String(assessment.assessment || "").toLocaleLowerCase("pt-BR")));
}

/** Applies the tenant's active rules to the latest available AI results. */
export async function evaluateInspectionRules(tenantId: string, externalId: string): Promise<RuleEvaluation> {
  const db = getDbPool();
  const [inspectionRows] = await db.execute("SELECT id, vehicle_type, inspection_cycle FROM inspections WHERE tenant_id = ? AND external_id = ? LIMIT 1", [tenantId, externalId]);
  const inspection = (inspectionRows as DbRow[])[0];
  if (!inspection) throw new Error("Vistoria não encontrada.");

  const [ruleSetRows] = await db.execute(
    "SELECT id, version, confidence_threshold, approved_limit, review_limit, rejected_limit FROM rule_sets WHERE tenant_id = ? AND vehicle_type = ? AND status = 'active' ORDER BY created_at DESC LIMIT 1",
    [tenantId, String(inspection.vehicle_type)],
  );
  const ruleSet = (ruleSetRows as DbRow[])[0];
  if (!ruleSet) throw new Error("Não existe um conjunto de regras ativo para este cliente e tipo de veículo.");

  const [rulesRows] = await db.execute("SELECT id, code, name, points, action, condition_json FROM rules WHERE rule_set_id = ? AND is_enabled = TRUE", [String(ruleSet.id)]);
  const [photoRows] = await db.execute(
    `SELECT p.id, p.category_code, p.ai_status, p.ai_quality_score, a.status AS analysis_status, a.confidence, a.summary, a.findings, a.requires_human, a.plate_match
       FROM inspection_photos p LEFT JOIN inspection_photo_analyses a ON a.inspection_photo_id = p.id WHERE p.inspection_id = ? AND p.inspection_cycle = ?`,
    [String(inspection.id), Number(inspection.inspection_cycle || 1)],
  );
  const photos = photoRows as DbRow[];
  if (!photos.length) throw new Error("Ainda não há fotos para avaliar nesta vistoria.");

  const [manualRows] = await db.execute(
    `SELECT m.inspection_photo_id, m.assessment, m.damage_location
     FROM inspection_photo_manual_assessments m
     INNER JOIN inspection_photos p ON p.id = m.inspection_photo_id
     WHERE p.inspection_id = ? AND p.inspection_cycle = ?`,
    [String(inspection.id), Number(inspection.inspection_cycle || 1)],
  );
  const manualAssessments = manualRows as DbRow[];
  // A classificação humana é a fonte definitiva para a foto avaliada e evita somar a IA duas vezes.
  const manuallyAssessedPhotoIds = new Set(manualAssessments.map((assessment) => String(assessment.inspection_photo_id)));
  const aiPhotos = photos.filter((photo) => !manuallyAssessedPhotoIds.has(String(photo.id)));
  const rules = rulesRows as Rule[];
  const aiTriggered: TriggeredRule[] = rules
    .filter((rule) => (json(rule.condition_json) as Condition).type !== "manual_assessment" && matches(rule, aiPhotos))
    .map((rule) => ({ code: rule.code, name: rule.name, points: Number(rule.points), action: rule.action, source: "ai_analysis" }));
  const manualTriggered: TriggeredRule[] = rules.flatMap((rule) => manualMatches(rule, manualAssessments).map((assessment) => ({
    code: rule.code,
    name: rule.name,
    points: Number(rule.points),
    action: rule.action,
    source: "human_assessment" as const,
    evidence: { photoId: String(assessment.inspection_photo_id), assessment: String(assessment.assessment), damageLocation: assessment.damage_location || null },
  })));
  const capturePolicy = await getCapturePolicy(tenantId);
  const hasBlockedPlateMismatch = capturePolicy.plateMismatchAction === "block_approval"
    && photos.some((photo) => String(photo.plate_match) === "mismatched");
  const policyTriggered: TriggeredRule[] = hasBlockedPlateMismatch ? [{
    code: "POLICY_PLATE_DIVERGENCE",
    name: "Placa divergente — bloqueio de aprovação configurado pelo cliente",
    points: 0,
    action: "reject",
    source: "ai_analysis",
    evidence: { plateMatch: "mismatched", policy: "block_approval" },
  }] : [];
  const triggered = [...aiTriggered, ...manualTriggered, ...policyTriggered];
  const score = triggered.reduce((total, rule) => total + rule.points, 0);
  const actions = new Set(triggered.map((rule) => rule.action));
  let decision: RuleEvaluation["decision"] = "approved";
  if (actions.has("retriage")) decision = "retriage";
  else if (actions.has("reject") || score >= Number(ruleSet.rejected_limit)) decision = "rejected";
  else if (actions.has("human_review") || score >= Number(ruleSet.review_limit)) decision = "human_review";
  else if (score > Number(ruleSet.approved_limit)) decision = "approved_with_notes";

  await db.execute("DELETE FROM inspection_rule_evaluations WHERE inspection_id = ?", [String(inspection.id)]);
  for (const rule of triggered) {
    await db.execute(
      "INSERT INTO inspection_rule_evaluations (id, inspection_id, rule_set_id, rule_code, rule_name, points, action, evidence) VALUES (?, ?, ?, ?, ?, ?, ?, ?)",
      [randomUUID(), String(inspection.id), String(ruleSet.id), rule.code, rule.name, rule.points, rule.action, JSON.stringify({ source: rule.source, ...(rule.evidence || {}) })],
    );
  }
  await db.execute("UPDATE inspections SET risk_score = ?, rules_version = ?, status = ?, completed_at = CASE WHEN ? IN ('approved','approved_with_notes','rejected') THEN NOW() ELSE NULL END WHERE id = ?", [score, String(ruleSet.version), decision, decision, String(inspection.id)]);
  if (decision === "human_review") {
    const [pending] = await db.execute("SELECT id FROM inspection_reviews WHERE inspection_id = ? AND review_status = 'requested' LIMIT 1", [String(inspection.id)]);
    if (!(pending as DbRow[])[0]) await db.execute("INSERT INTO inspection_reviews (id, inspection_id, review_status, reason, rules_version) VALUES (?, ?, 'requested', ?, ?)", [randomUUID(), String(inspection.id), "Regras automáticas solicitaram validação humana.", String(ruleSet.version)]);
  }
  return { score, decision, rulesVersion: String(ruleSet.version), triggered };
}
