import { createHash } from "node:crypto";
import { promises as fs } from "node:fs";
import { isAbsolute, join, relative, resolve } from "node:path";

import {
  listField,
  parseMarkdownFrontmatter,
  strField,
} from "../ledger/frontmatter.ts";
import { decodeUtf8Strict } from "./TextEncodingGate.ts";

export const FACT_SOURCE_ROOT_REL = "fcop/shared/fact-sources";
export const FACT_SOURCE_STANDARDS_REL = `${FACT_SOURCE_ROOT_REL}/standards`;
export const FACT_SOURCE_SOURCES_REL = `${FACT_SOURCE_ROOT_REL}/sources`;

export const FACT_SOURCE_ADAPTER_IDS = [
  "json_file",
  "csv_file",
  "markdown_attachment",
  "inline_frontmatter",
] as const;

export const FACT_VALIDATOR_IDS = [
  "required",
  "value_equals",
  "value_in_set",
  "format_matches",
  "schema_valid",
  "range",
  "checksum",
] as const;

export type FactSourceAdapterId = typeof FACT_SOURCE_ADAPTER_IDS[number];
export type FactValidatorId = typeof FACT_VALIDATOR_IDS[number];
export type ThirdPartySourceState =
  | "not_configured"
  | "verified"
  | "conflict"
  | "unavailable"
  | "invalid";

export interface FactSourceDefinition {
  id: string;
  filename: string;
  path: string;
  title: string;
  status: "active" | "disabled";
  source_type: "data_source" | "attachment_rule";
  adapter: FactSourceAdapterId;
  location?: string;
  sensitive_fields: string[];
  body: string;
}

export interface FactStandardValidator {
  check_id: string;
  validator_id: FactValidatorId;
  source_id?: string;
  actual_path: string;
  expected_path?: string;
  expected_value?: unknown;
  allowed_values?: unknown[];
  pattern?: string;
  required_fields?: string[];
  minimum?: number;
  maximum?: number;
  sensitive?: boolean;
}

export interface FactStandardDefinition {
  id: string;
  filename: string;
  path: string;
  title: string;
  status: "active" | "disabled";
  source_ids: string[];
  validators: FactStandardValidator[];
  body: string;
}

export interface FactSourceRegistryIssue {
  code: string;
  file?: string;
  message: string;
}

export interface FactSourceRegistrySnapshot {
  schema_version: "1.0";
  root: string;
  layout_ready: boolean;
  sources: FactSourceDefinition[];
  standards: FactStandardDefinition[];
  issues: FactSourceRegistryIssue[];
  supported_adapters: readonly FactSourceAdapterId[];
  supported_validators: readonly FactValidatorId[];
}

export interface ThirdPartyFactCheckResult {
  state: ThirdPartySourceState;
  source_ids: string[];
  standard_ids: string[];
  checks: Array<{
    check_id: string;
    validator_id: FactValidatorId;
    state: Exclude<ThirdPartySourceState, "not_configured">;
    source_id?: string;
    actual_digest?: string;
    expected_digest?: string;
    detail: string;
  }>;
  conflicts: string[];
  warnings: string[];
}

function object(value: unknown): Record<string, unknown> | null {
  return value && typeof value === "object" && !Array.isArray(value)
    ? value as Record<string, unknown>
    : null;
}

function strings(value: unknown): string[] {
  if (Array.isArray(value)) {
    return value.map(String).map((item) => item.trim()).filter(Boolean);
  }
  if (value == null) return [];
  return String(value).split(/[\n,]/).map((item) => item.trim()).filter(Boolean);
}

function bodyAfterFrontmatter(raw: string): string {
  if (!raw.startsWith("---")) return raw.trim();
  const end = raw.indexOf("\n---", 3);
  return end < 0 ? "" : raw.slice(end + 4).trim();
}

function digest(value: unknown): string {
  const stable = JSON.stringify(value, (_key, item) => {
    if (!item || typeof item !== "object" || Array.isArray(item)) return item;
    return Object.fromEntries(Object.entries(item as Record<string, unknown>).sort());
  }) ?? "undefined";
  return `sha256:${createHash("sha256").update(stable).digest("hex")}`;
}

function id(value: unknown): string {
  return String(value ?? "").trim().toUpperCase();
}

function status(value: unknown): "active" | "disabled" {
  return String(value ?? "active").trim().toLowerCase() === "disabled"
    ? "disabled"
    : "active";
}

function isSafeId(value: string, prefix: "FACT-SOURCE-" | "FACT-STANDARD-"): boolean {
  return new RegExp(`^${prefix}[A-Z0-9][A-Z0-9_-]*$`).test(value);
}

function normalizeValidator(value: unknown, index: number): FactStandardValidator | null {
  const row = object(value);
  if (!row) return null;
  const validatorId = String(row["validator_id"] ?? "").trim() as FactValidatorId;
  if (!FACT_VALIDATOR_IDS.includes(validatorId)) return null;
  const expectedValue = row["expected_value"];
  const allowedValues = Array.isArray(row["allowed_values"])
    ? row["allowed_values"]
    : undefined;
  const minimum = Number(row["minimum"]);
  const maximum = Number(row["maximum"]);
  return {
    check_id: String(row["check_id"] ?? `CHECK-${index + 1}`).trim(),
    validator_id: validatorId,
    ...(row["source_id"] ? { source_id: id(row["source_id"]) } : {}),
    actual_path: String(row["actual_path"] ?? "").trim(),
    ...(row["expected_path"] ? { expected_path: String(row["expected_path"]).trim() } : {}),
    ...(expectedValue !== undefined ? { expected_value: expectedValue } : {}),
    ...(allowedValues ? { allowed_values: allowedValues } : {}),
    ...(row["pattern"] ? { pattern: String(row["pattern"]) } : {}),
    ...(strings(row["required_fields"]).length
      ? { required_fields: strings(row["required_fields"]) }
      : {}),
    ...(Number.isFinite(minimum) ? { minimum } : {}),
    ...(Number.isFinite(maximum) ? { maximum } : {}),
    ...(row["sensitive"] === true ? { sensitive: true } : {}),
  };
}

async function markdownFiles(dir: string): Promise<string[]> {
  try {
    return (await fs.readdir(dir, { withFileTypes: true }))
      .filter((entry) => entry.isFile() && entry.name.toLowerCase().endsWith(".md"))
      .map((entry) => entry.name)
      .sort();
  } catch {
    return [];
  }
}

export async function inspectFactSourceRegistry(
  projectRoot: string,
): Promise<FactSourceRegistrySnapshot> {
  const registryRoot = join(resolve(projectRoot), FACT_SOURCE_ROOT_REL);
  const sourcesDir = join(resolve(projectRoot), FACT_SOURCE_SOURCES_REL);
  const standardsDir = join(resolve(projectRoot), FACT_SOURCE_STANDARDS_REL);
  const [sourceNames, standardNames] = await Promise.all([
    markdownFiles(sourcesDir),
    markdownFiles(standardsDir),
  ]);
  const issues: FactSourceRegistryIssue[] = [];
  const sources: FactSourceDefinition[] = [];
  const standards: FactStandardDefinition[] = [];
  const sourceIds = new Set<string>();
  const standardIds = new Set<string>();

  for (const filename of sourceNames) {
    if (/^(?:README|TEMPLATE)/i.test(filename)) continue;
    const path = join(sourcesDir, filename);
    const decoded = await fs.readFile(path).then(decodeUtf8Strict).catch(() => null);
    if (!decoded?.text || decoded.check.state !== "verified") {
      issues.push({ code: "FACT_SOURCE_ENCODING_INVALID", file: filename, message: "事实源文档不是无乱码的严格 UTF-8" });
      continue;
    }
    const raw = decoded.text;
    const fm = parseMarkdownFrontmatter(raw);
    const sourceId = id(fm["fact_source_id"] ?? fm["source_id"]);
    const adapter = strField(fm, "adapter") as FactSourceAdapterId;
    const sourceType = strField(fm, "source_type") as FactSourceDefinition["source_type"];
    if (!isSafeId(sourceId, "FACT-SOURCE-")) {
      issues.push({ code: "FACT_SOURCE_ID_INVALID", file: filename, message: "fact_source_id 缺失或格式无效" });
      continue;
    }
    if (sourceIds.has(sourceId)) {
      issues.push({ code: "FACT_SOURCE_ID_DUPLICATE", file: filename, message: `事实源 ID 重复：${sourceId}` });
      continue;
    }
    sourceIds.add(sourceId);
    if (!FACT_SOURCE_ADAPTER_IDS.includes(adapter)) {
      issues.push({ code: "FACT_SOURCE_ADAPTER_UNSUPPORTED", file: filename, message: `${sourceId} 使用未注册 adapter：${adapter || "(empty)"}` });
      continue;
    }
    if (sourceType !== "data_source" && sourceType !== "attachment_rule") {
      issues.push({ code: "FACT_SOURCE_TYPE_INVALID", file: filename, message: `${sourceId} 的 source_type 无效` });
      continue;
    }
    sources.push({
      id: sourceId,
      filename,
      path,
      title: strField(fm, "title") || sourceId,
      status: status(fm["status"]),
      source_type: sourceType,
      adapter,
      ...(strField(fm, "location") ? { location: strField(fm, "location") } : {}),
      sensitive_fields: listField(fm, "sensitive_fields"),
      body: bodyAfterFrontmatter(raw),
    });
  }

  for (const filename of standardNames) {
    if (/^(?:README|TEMPLATE)/i.test(filename)) continue;
    const path = join(standardsDir, filename);
    const decoded = await fs.readFile(path).then(decodeUtf8Strict).catch(() => null);
    if (!decoded?.text || decoded.check.state !== "verified") {
      issues.push({ code: "FACT_STANDARD_ENCODING_INVALID", file: filename, message: "事实标准文档不是无乱码的严格 UTF-8" });
      continue;
    }
    const raw = decoded.text;
    const fm = parseMarkdownFrontmatter(raw);
    const standardId = id(fm["fact_standard_id"] ?? fm["standard_id"]);
    if (!isSafeId(standardId, "FACT-STANDARD-")) {
      issues.push({ code: "FACT_STANDARD_ID_INVALID", file: filename, message: "fact_standard_id 缺失或格式无效" });
      continue;
    }
    if (standardIds.has(standardId)) {
      issues.push({ code: "FACT_STANDARD_ID_DUPLICATE", file: filename, message: `事实标准 ID 重复：${standardId}` });
      continue;
    }
    standardIds.add(standardId);
    const rawValidators = Array.isArray(fm["validators"]) ? fm["validators"] as unknown[] : [];
    const validators = rawValidators
      .map((row, index) => normalizeValidator(row, index))
      .filter((row): row is FactStandardValidator => Boolean(row));
    if (rawValidators.length !== validators.length || validators.some((row) => !row.actual_path)) {
      issues.push({ code: "FACT_STANDARD_VALIDATOR_INVALID", file: filename, message: `${standardId} 包含无效 validator 配置` });
      continue;
    }
    standards.push({
      id: standardId,
      filename,
      path,
      title: strField(fm, "title") || standardId,
      status: status(fm["status"]),
      source_ids: listField(fm, "source_ids").map(id),
      validators,
      body: bodyAfterFrontmatter(raw),
    });
  }

  for (const standard of standards) {
    for (const sourceId of standard.source_ids) {
      if (!sourceIds.has(sourceId)) {
        issues.push({ code: "FACT_STANDARD_SOURCE_MISSING", file: standard.filename, message: `${standard.id} 引用了不存在的 ${sourceId}` });
      }
    }
    for (const validator of standard.validators) {
      if (validator.source_id && !sourceIds.has(validator.source_id)) {
        issues.push({ code: "FACT_VALIDATOR_SOURCE_MISSING", file: standard.filename, message: `${validator.check_id} 引用了不存在的 ${validator.source_id}` });
      }
    }
  }

  const layoutReady = await Promise.all([
    fs.access(sourcesDir).then(() => true).catch(() => false),
    fs.access(standardsDir).then(() => true).catch(() => false),
  ]).then((rows) => rows.every(Boolean));

  return {
    schema_version: "1.0",
    root: registryRoot,
    layout_ready: layoutReady,
    sources,
    standards,
    issues,
    supported_adapters: FACT_SOURCE_ADAPTER_IDS,
    supported_validators: FACT_VALIDATOR_IDS,
  };
}

function safeProjectPath(projectRoot: string, value: string): string | null {
  if (!value.trim()) return null;
  const root = resolve(projectRoot);
  const path = resolve(isAbsolute(value) ? value : join(root, value));
  const rel = relative(root, path);
  return !rel || (!rel.startsWith("..") && !isAbsolute(rel)) ? path : null;
}

function getPath(value: unknown, path: string): unknown {
  if (!path) return value;
  let cursor = value;
  for (const segment of path.split(".").filter(Boolean)) {
    if (Array.isArray(cursor) && /^\d+$/.test(segment)) {
      cursor = cursor[Number(segment)];
      continue;
    }
    const row = object(cursor);
    if (!row || !(segment in row)) return undefined;
    cursor = row[segment];
  }
  return cursor;
}

function parseCsvLine(line: string): string[] {
  const values: string[] = [];
  let current = "";
  let quoted = false;
  for (let index = 0; index < line.length; index += 1) {
    const char = line[index]!;
    if (char === '"') {
      if (quoted && line[index + 1] === '"') {
        current += '"';
        index += 1;
      } else {
        quoted = !quoted;
      }
    } else if (char === "," && !quoted) {
      values.push(current.trim());
      current = "";
    } else {
      current += char;
    }
  }
  values.push(current.trim());
  return values;
}

function parseCsv(content: string): { rows: Record<string, string>[] } {
  const lines = content.split(/\r?\n/).filter((line) => line.trim());
  if (!lines.length) return { rows: [] };
  const headers = parseCsvLine(lines[0]!);
  return {
    rows: lines.slice(1).map((line) => Object.fromEntries(
      parseCsvLine(line).map((item, index) => [headers[index] ?? String(index), item]),
    )),
  };
}

async function loadSourceData(
  projectRoot: string,
  source: FactSourceDefinition,
): Promise<{ ok: true; value: unknown } | { ok: false; warning: string }> {
  if (source.adapter === "inline_frontmatter") {
    const decoded = await fs.readFile(source.path).then(decodeUtf8Strict).catch(() => null);
    if (!decoded?.text || decoded.check.state !== "verified") {
      return { ok: false, warning: `${source.id} 文档编码不是严格 UTF-8` };
    }
    return { ok: true, value: parseMarkdownFrontmatter(decoded.text) };
  }
  const location = source.location ? safeProjectPath(projectRoot, source.location) : null;
  if (!location) return { ok: false, warning: `${source.id} 未配置安全的 location` };
  try {
    const [realRoot, realLocation] = await Promise.all([
      fs.realpath(resolve(projectRoot)),
      fs.realpath(location),
    ]);
    const rel = relative(realRoot, realLocation);
    if (rel.startsWith("..") || isAbsolute(rel)) {
      return { ok: false, warning: `${source.id} 的 location 通过链接越过项目根` };
    }
  } catch {
    return { ok: false, warning: `${source.id} 的 location 不存在或不可访问：${source.location}` };
  }
  const decoded = await fs.readFile(location).then(decodeUtf8Strict).catch(() => null);
  const raw = decoded?.text ?? "";
  if (!raw || decoded?.check.state !== "verified") {
    return { ok: false, warning: `${source.id} 数据不可用或编码无效：${source.location}` };
  }
  try {
    if (source.adapter === "json_file") return { ok: true, value: JSON.parse(raw) };
    if (source.adapter === "csv_file") return { ok: true, value: parseCsv(raw) };
    const fm = parseMarkdownFrontmatter(raw);
    return { ok: true, value: { frontmatter: fm, body: bodyAfterFrontmatter(raw) } };
  } catch {
    return { ok: false, warning: `${source.id} 数据无法按 ${source.adapter} 解析` };
  }
}

function empty(value: unknown): boolean {
  return value == null || value === "" || (Array.isArray(value) && value.length === 0);
}

function validate(
  rule: FactStandardValidator,
  actual: unknown,
  expected: unknown,
): boolean {
  switch (rule.validator_id) {
    case "required":
      return !empty(actual);
    case "value_equals":
      return JSON.stringify(actual) === JSON.stringify(expected);
    case "value_in_set":
      return (rule.allowed_values ?? (Array.isArray(expected) ? expected : [])).some(
        (item) => JSON.stringify(item) === JSON.stringify(actual),
      );
    case "format_matches":
      try {
        return new RegExp(rule.pattern ?? String(expected ?? "")).test(String(actual ?? ""));
      } catch {
        return false;
      }
    case "schema_valid": {
      const row = object(actual);
      return Boolean(row && (rule.required_fields ?? []).every((field) => getPath(row, field) !== undefined));
    }
    case "range": {
      const number = Number(actual);
      return Number.isFinite(number) &&
        (rule.minimum == null || number >= rule.minimum) &&
        (rule.maximum == null || number <= rule.maximum);
    }
    case "checksum":
      return digest(actual) === String(expected ?? "");
  }
}

export async function evaluateBoundThirdPartyFacts(input: {
  projectRoot: string;
  taskFrontmatter: Record<string, unknown>;
  reportFrontmatter: Record<string, unknown>;
}): Promise<ThirdPartyFactCheckResult> {
  const sourceIds = listField(input.taskFrontmatter, "fact_source_ids").map(id);
  const standardIds = listField(input.taskFrontmatter, "fact_standard_ids").map(id);
  if (!sourceIds.length && !standardIds.length) {
    return { state: "not_configured", source_ids: [], standard_ids: [], checks: [], conflicts: [], warnings: [] };
  }
  const registry = await inspectFactSourceRegistry(input.projectRoot);
  if (registry.issues.length) {
    return {
      state: "invalid",
      source_ids: sourceIds,
      standard_ids: standardIds,
      checks: [],
      conflicts: registry.issues.map((issue) => issue.message),
      warnings: [],
    };
  }
  const boundStandards = standardIds
    .map((standardId) => registry.standards.find((row) => row.id === standardId && row.status === "active"))
    .filter((row): row is FactStandardDefinition => Boolean(row));
  if (boundStandards.length !== standardIds.length || !boundStandards.length) {
    return {
      state: "invalid",
      source_ids: sourceIds,
      standard_ids: standardIds,
      checks: [],
      conflicts: ["任务绑定的事实标准不存在、已禁用，或仅绑定事实源但没有校验标准"],
      warnings: [],
    };
  }
  const allSourceIds = [...new Set([
    ...sourceIds,
    ...boundStandards.flatMap((standard) => standard.source_ids),
    ...boundStandards.flatMap((standard) => standard.validators.map((row) => row.source_id).filter(Boolean) as string[]),
  ])];
  const sourceData = new Map<string, unknown>();
  const warnings: string[] = [];
  for (const sourceId of allSourceIds) {
    const source = registry.sources.find((row) => row.id === sourceId && row.status === "active");
    if (!source) {
      warnings.push(`${sourceId} 不存在或已禁用`);
      continue;
    }
    const loaded = await loadSourceData(input.projectRoot, source);
    if (!loaded.ok) warnings.push(loaded.warning);
    else sourceData.set(sourceId, loaded.value);
  }
  if (warnings.length) {
    return { state: "unavailable", source_ids: allSourceIds, standard_ids: standardIds, checks: [], conflicts: [], warnings };
  }

  const reportedFacts = object(input.reportFrontmatter["reported_facts"]) ?? {};
  const checks: ThirdPartyFactCheckResult["checks"] = [];
  const conflicts: string[] = [];
  for (const standard of boundStandards) {
    for (const rule of standard.validators) {
      const sourceId = rule.source_id ?? standard.source_ids[0];
      const actual = getPath(reportedFacts, rule.actual_path);
      const expected = rule.expected_value !== undefined
        ? rule.expected_value
        : getPath(sourceId ? sourceData.get(sourceId) : undefined, rule.expected_path ?? "");
      const ok = validate(rule, actual, expected);
      if (!ok) conflicts.push(`${standard.id}/${rule.check_id} 未通过 ${rule.validator_id}`);
      checks.push({
        check_id: rule.check_id,
        validator_id: rule.validator_id,
        state: ok ? "verified" : "conflict",
        ...(sourceId ? { source_id: sourceId } : {}),
        actual_digest: digest(actual),
        ...(rule.validator_id === "required" ? {} : { expected_digest: digest(expected) }),
        detail: ok ? "已按注册事实标准核验" : "报告事实与注册标准不一致或缺失",
      });
    }
  }
  return {
    state: conflicts.length ? "conflict" : "verified",
    source_ids: allSourceIds,
    standard_ids: standardIds,
    checks,
    conflicts,
    warnings: [],
  };
}
