import fs from "node:fs";
import path from "node:path";
import { describe, expect, it } from "vitest";
import { enrichProblemUnitLifecycle } from "../src/services/lifecycleRuntime";
import type { CandidateEvidenceItem, ProblemUnit } from "../src/types/stage2ProblemUnits";

type Stage3LifecycleHints = {
  expected_lifecycle_domain?: string;
  require_current_expected_state_pair?: boolean;
  require_missing_or_invalid_transition?: boolean;
  require_previous_states?: boolean;
  require_terminal_state_mismatch?: boolean;
  require_wrong_closing_document_type?: boolean;
  require_cross_branch_conflict?: boolean;
  require_period_close_impact?: boolean;
};

type Stage3LifecycleProbeCase = {
  case_id: string;
  expected_hints?: Stage3LifecycleHints;
  lifecycle_focus?: {
    domain?: string;
  };
};

type Stage3LifecycleProbeSuite = {
  suite_id: string;
  scenario_count: number;
  cases: Stage3LifecycleProbeCase[];
};

function loadSuite(): Stage3LifecycleProbeSuite {
  const suitePath = path.resolve(process.cwd(), "../eval_cases/assistant_stage3_lifecycle_probe_v0_1.json");
  const raw = fs.readFileSync(suitePath, "utf8").replace(/^\uFEFF/, "");
  return JSON.parse(raw) as Stage3LifecycleProbeSuite;
}

function buildProblemUnit(input: {
  id: string;
  type: ProblemUnit["problem_unit_type"];
  mechanismSummary: string;
  businessDefectClass: string;
  affectedAccounts: string[];
  actualState?: string;
  expectedState?: string;
  failedExpectedEdge?: string;
  periodCloseRisk?: boolean;
}): ProblemUnit {
  return {
    schema_version: "problem_unit_v0_1",
    problem_unit_id: input.id,
    problem_unit_type: input.type,
    title: "Synthetic Stage3 lifecycle probe unit",
    mechanism_summary: input.mechanismSummary,
    business_defect_class: input.businessDefectClass,
    severity: {
      score: 0.78,
      grade: "high"
    },
    confidence: {
      score: 0.66,
      grade: "medium"
    },
    affected_entities: ["Document:DOC-1"],
    affected_documents: ["Document:DOC-1"],
    affected_postings: [],
    affected_accounts: input.affectedAccounts,
    affected_counterparties: ["Counterparty:CP-1"],
    affected_contracts: ["Contract:CTR-1"],
    ...(input.actualState ? { actual_state: input.actualState } : {}),
    ...(input.expectedState ? { expected_state: input.expectedState } : {}),
    ...(input.failedExpectedEdge ? { failed_expected_edge: input.failedExpectedEdge } : {}),
    ...(input.periodCloseRisk
      ? {
          period_impact: {
            is_period_sensitive: true,
            impact_class: "close_risk" as const
          }
        }
      : {}),
    evidence_pack: ["cand-1"],
    entity_backlinks: [{ entity: "Document", id: "DOC-1" }],
    snapshot_limitations: []
  };
}

function buildCandidate(input: {
  id: string;
  anomalies: string[];
  relations: string[];
  confidenceHint?: "high" | "medium" | "low";
}): CandidateEvidenceItem {
  return {
    schema_version: "candidate_evidence_v0_1",
    candidate_id: input.id,
    route: "hybrid_store_plus_live",
    source_ref: {
      schema_version: "evidence_source_ref_v1",
      namespace: "snapshot_2020",
      entity: "Document",
      id: "DOC-1",
      period: "2020-06",
      canonical_ref: "evidence_source_ref_v1|snapshot_2020|document|doc-1|2020-06"
    },
    relation_pattern_hits: input.relations,
    anomaly_patterns: input.anomalies,
    entity_backlinks: [{ entity: "Document", id: "DOC-1" }],
    confidence_hint: input.confidenceHint ?? "medium"
  };
}

function buildSyntheticInput(probeCase: Stage3LifecycleProbeCase): { unit: ProblemUnit; candidates: CandidateEvidenceItem[] } {
  const hints = probeCase.expected_hints ?? {};
  const domainFocus = probeCase.lifecycle_focus?.domain ?? "51_60";

  const anomalies = new Set<string>();
  const relations = new Set<string>();

  let problemType: ProblemUnit["problem_unit_type"] = "broken_chain_segment";
  let mechanismSummary = "bank settlement lifecycle chain";
  let businessDefectClass = "broken_lifecycle";
  let affectedAccounts = ["51", "60"];
  let actualState: string | undefined;
  let expectedState: string | undefined;
  let failedExpectedEdge: string | undefined;
  let periodCloseRisk = false;

  if (domainFocus === "97") {
    problemType = "lifecycle_anomaly_node";
    mechanismSummary = "deferred writeoff lifecycle chain for account 97";
    businessDefectClass = "missing_expected_transition";
    affectedAccounts = ["97"];
    relations.add("writeoff_partial");
    expectedState = "fully_written_off";
  } else if (domainFocus === "fixed_asset") {
    problemType = "document_conflict";
    mechanismSummary = "fixed asset depreciation lifecycle for accounts 01 02";
    businessDefectClass = "cross_branch_inconsistency";
    affectedAccounts = ["01", "02"];
    relations.add("depreciation_register_movement");
    expectedState = "depreciation_active";
  } else if (domainFocus === "vat_flow") {
    problemType = "cross_branch_inconsistency_cluster";
    mechanismSummary = "vat lifecycle flow for accounts 19 68";
    businessDefectClass = "cross_branch_inconsistency";
    affectedAccounts = ["19", "68"];
    relations.add("invoice_to_vat");
    expectedState = "vat_deducted";
  } else if (domainFocus === "period_close") {
    problemType = "period_risk_cluster";
    mechanismSummary = "period close lifecycle blocker for close operation";
    businessDefectClass = "period_close_risk";
    affectedAccounts = ["51", "60"];
    periodCloseRisk = true;
    expectedState = "close_completed";
  } else {
    relations.add("payment_to_settlement");
    expectedState = "settlement_closed";
  }

  if (hints.require_missing_or_invalid_transition) {
    anomalies.add("missing_link");
    anomalies.add("no_continuation");
    failedExpectedEdge = "expected_transition_not_observed";
  }

  if (hints.require_wrong_closing_document_type) {
    anomalies.add("wrong_document_type");
    anomalies.add("posting_mismatch");
  }

  if (hints.require_cross_branch_conflict) {
    anomalies.add("cross_branch_inconsistency");
  }

  if (hints.require_period_close_impact) {
    anomalies.add("period_close_risk");
    periodCloseRisk = true;
  }

  if (hints.require_previous_states) {
    actualState = domainFocus === "97" ? "partially_written_off" : "bank_recorded";
    if (!expectedState) {
      expectedState = domainFocus === "97" ? "fully_written_off" : "settlement_closed";
    }
  }

  if (hints.require_terminal_state_mismatch) {
    if (!actualState) {
      if (domainFocus === "97") actualState = "partially_written_off";
      else if (domainFocus === "fixed_asset") actualState = "depreciation_active";
      else if (domainFocus === "vat_flow") actualState = "vat_registered";
      else actualState = "bank_recorded";
    }
    if (domainFocus === "fixed_asset") expectedState = "disposed";
    else if (domainFocus === "vat_flow") expectedState = "vat_deducted";
    else if (domainFocus === "97") expectedState = "fully_written_off";
    else expectedState = "settlement_closed";
  }

  const unit = buildProblemUnit({
    id: `probe-${probeCase.case_id.toLowerCase()}`,
    type: problemType,
    mechanismSummary,
    businessDefectClass,
    affectedAccounts,
    actualState,
    expectedState,
    failedExpectedEdge,
    periodCloseRisk
  });

  const candidates = [
    buildCandidate({
      id: `cand-${probeCase.case_id.toLowerCase()}`,
      anomalies: Array.from(anomalies),
      relations: Array.from(relations)
    })
  ];

  return {
    unit,
    candidates
  };
}

describe("stage3 lifecycle probe semantics", () => {
  it("validates lifecycle acceptance targets on synthetic runtime inputs", () => {
    const suite = loadSuite();
    expect(suite.suite_id).toBe("assistant_stage3_lifecycle_probe");
    expect(suite.scenario_count).toBe(suite.cases.length);

    for (const probeCase of suite.cases) {
      const hints = probeCase.expected_hints ?? {};
      const { unit, candidates } = buildSyntheticInput(probeCase);
      const enriched = enrichProblemUnitLifecycle({ unit, candidates });

      if (typeof hints.expected_lifecycle_domain === "string" && hints.expected_lifecycle_domain.length > 0) {
        expect(enriched.lifecycle_domain, `${probeCase.case_id}: expected lifecycle domain`).toBe(hints.expected_lifecycle_domain);
      }

      if (hints.require_current_expected_state_pair) {
        expect(typeof enriched.current_lifecycle_state, `${probeCase.case_id}: current state`).toBe("string");
        expect(typeof enriched.expected_lifecycle_state, `${probeCase.case_id}: expected state`).toBe("string");
      }

      if (hints.require_missing_or_invalid_transition) {
        expect(
          Boolean(enriched.missing_transition || enriched.invalid_transition),
          `${probeCase.case_id}: missing/invalid transition`
        ).toBe(true);
      }

      if (hints.require_previous_states) {
        const previousStates = Array.isArray(enriched.lifecycle_resolution?.resolved_previous_states)
          ? enriched.lifecycle_resolution?.resolved_previous_states
          : [];
        expect(previousStates.length, `${probeCase.case_id}: resolved_previous_states`).toBeGreaterThan(0);
      }

      if (hints.require_wrong_closing_document_type) {
        const defect = String(enriched.lifecycle_defect_type ?? "");
        expect(["misclosed_state", "invalid_transition"].includes(defect), `${probeCase.case_id}: wrong close defect`).toBe(true);
      }

      if (hints.require_cross_branch_conflict) {
        expect(enriched.lifecycle_defect_type, `${probeCase.case_id}: cross-branch defect`).toBe("cross_branch_state_conflict");
      }

      if (hints.require_terminal_state_mismatch) {
        const defect = String(enriched.lifecycle_defect_type ?? "");
        const current = String(enriched.current_lifecycle_state ?? "");
        const expected = String(enriched.expected_lifecycle_state ?? "");
        const hasMismatchSignal =
          defect === "premature_terminal_state" ||
          defect === "misclosed_state" ||
          defect === "orphan_intermediate_state" ||
          (current.length > 0 && expected.length > 0 && current !== expected);
        expect(hasMismatchSignal, `${probeCase.case_id}: terminal mismatch signal`).toBe(true);
      }

      if (hints.require_period_close_impact) {
        const hasPeriodCloseImpact = Array.isArray(enriched.lifecycle_ranking_basis)
          ? enriched.lifecycle_ranking_basis.includes("period_close_impact")
          : false;
        expect(hasPeriodCloseImpact || enriched.lifecycle_domain === "period_close", `${probeCase.case_id}: period close impact`).toBe(true);
      }
    }
  });
});
