import { mkdirSync, writeFileSync } from "node:fs";
import { resolve } from "node:path";
import { describe, it, expect, beforeAll, afterAll } from "vitest";
import {
  handleDevAuth,
  handleDevAuthMe,
  handleDevOnboarding,
  handleDevReferenceOptions,
  encodeDevToken,
} from "@/lib/dev-api-mock";
import { isAllowedSatelliteUrl } from "@/lib/satellite-image-allowlist";
import { normalizeScore } from "@/lib/scoring-normalize";
import {
  SYSTEM_FUNCTIONAL_FLOW,
  DB_API_SYNC_MATRIX,
  type ApiFlowStep,
} from "@/lib/e2e/api-flow-registry";
import {
  assertOtpSentResponse,
  assertVerifyResponse,
  assertUserMeResponse,
  assertOnboardingSession,
  assertReferenceOptions,
} from "../contract/schema-assert";

export interface FlowLogEntry {
  stepId: string;
  phase: string;
  method: string;
  path: string;
  status: "pass" | "fail" | "skip";
  httpStatus?: number;
  durationMs: number;
  message?: string;
  timestamp: string;
}

export const flowLog: FlowLogEntry[] = [];

function logStep(
  step: ApiFlowStep,
  status: FlowLogEntry["status"],
  durationMs: number,
  extra?: { httpStatus?: number; message?: string },
) {
  flowLog.push({
    stepId: step.id,
    phase: step.phase,
    method: step.method,
    path: step.path,
    status,
    durationMs,
    httpStatus: extra?.httpStatus,
    message: extra?.message,
    timestamp: new Date().toISOString(),
  });
}

function findStep(id: string): ApiFlowStep {
  return SYSTEM_FUNCTIONAL_FLOW.find((s) => s.id === id)!;
}

describe("BFF API full functional flow (E2E via dev-mock)", () => {
  const phone = `+99890${Date.now().toString().slice(-7)}`;
  let token = "";
  let userId = "";
  let sessionId = "";

  beforeAll(() => {
    flowLog.length = 0;
  });

  it("F01–F03: Authentication chain", () => {
    const steps = ["F01", "F02", "F03"] as const;
    for (const id of steps) {
      const step = findStep(id);
      const t0 = Date.now();
      try {
        if (id === "F01") {
          const { status, data } = handleDevAuth("otp", "POST", { phone });
          assertOtpSentResponse(data);
          logStep(step, "pass", Date.now() - t0, { httpStatus: status });
        } else if (id === "F02") {
          const { status, data } = handleDevAuth("verify", "POST", { phone, code: "1234", countryCode: "UZ" });
          assertVerifyResponse(data);
          token = encodeDevToken((data as { user: { id: string } }).user as never);
          logStep(step, "pass", Date.now() - t0, { httpStatus: status });
        } else {
          const { status, data } = handleDevAuthMe(token);
          assertUserMeResponse(data);
          userId = (data as { id: string }).id;
          logStep(step, "pass", Date.now() - t0, { httpStatus: status });
        }
      } catch (e) {
        logStep(step, "fail", Date.now() - t0, { message: String(e) });
        throw e;
      }
    }
  });

  it("F04–F05: Onboarding session create + fetch", () => {
    for (const id of ["F04", "F05"] as const) {
      const step = findStep(id);
      const t0 = Date.now();
      try {
        if (id === "F04") {
          const { status, data } = handleDevOnboarding("", "POST", { countryCode: "UZ", channel: "farmer" }, userId, true);
          assertOnboardingSession(data);
          sessionId = (data as { id: string }).id;
          logStep(step, "pass", Date.now() - t0, { httpStatus: status });
        } else {
          const { status, data } = handleDevOnboarding(sessionId, "GET", null, userId, false);
          assertOnboardingSession(data);
          logStep(step, "pass", Date.now() - t0, { httpStatus: status });
        }
      } catch (e) {
        logStep(step, "fail", Date.now() - t0, { message: String(e) });
        throw e;
      }
    }
  });

  it("F06: Step persistence (FARM_PROFILE sample)", () => {
    const step = findStep("F06");
    const t0 = Date.now();
    try {
      const { status, data } = handleDevOnboarding(
        `${sessionId}/steps/FARM_PROFILE`,
        "PATCH",
        {
          data: {
            fullName: "Test Farmer",
            yearsFarming: 12,
            primaryLanguage: "uz",
            region: "Fergana",
            district: "Fergana",
          },
          markComplete: true,
        },
        userId,
        false,
      );
      assertOnboardingSession(data);
      const stepData = (data as { stepData: Record<string, unknown> }).stepData;
      expect(stepData.FARM_PROFILE).toBeDefined();
      logStep(step, "pass", Date.now() - t0, { httpStatus: status });
    } catch (e) {
      logStep(step, "fail", Date.now() - t0, { message: String(e) });
      throw e;
    }
  });

  it("F07–F08: Submit + scoring status poll", () => {
    const submitStep = findStep("F07");
    const statusStep = findStep("F08");
    const t0 = Date.now();
    try {
      const submit = handleDevOnboarding(`${sessionId}/submit`, "POST", null, userId, false);
      expect(submit.status).toBe(200);
      expect((submit.data as { status: string }).status).toBe("scoring");
      logStep(submitStep, "pass", Date.now() - t0, { httpStatus: submit.status });

      const t1 = Date.now();
      const status = handleDevOnboarding(`${sessionId}/status`, "GET", null, userId, false);
      expect(status.status).toBe(200);
      const payload = status.data as Record<string, unknown>;
      expect(["geo", "documents", "scoring", "finalize"]).toContain(payload.scoringPhase);
      logStep(statusStep, "pass", Date.now() - t1, { httpStatus: status.status, message: `phase=${payload.scoringPhase}` });
    } catch (e) {
      logStep(submitStep, "fail", Date.now() - t0, { message: String(e) });
      throw e;
    }
  });

  it("F09: Satellite image allowlist", () => {
    const step = findStep("F09");
    const t0 = Date.now();
    const url = "https://gibs.earthdata.nasa.gov/wmts/epsg4326/best/NDVI_Terra/default/2024-06-01/250m/5/20/12.png";
    expect(isAllowedSatelliteUrl(url)).toBe(true);
    expect(isAllowedSatelliteUrl("https://evil.example.com/x.png")).toBe(false);
    logStep(step, "pass", Date.now() - t0, { httpStatus: 200 });
  });

  it("F11: Reference options", () => {
    const step = findStep("F11");
    const t0 = Date.now();
    const { status, data } = handleDevReferenceOptions(new URLSearchParams({ type: "regions", locale: "uz" }));
    assertReferenceOptions(data);
    logStep(step, "pass", Date.now() - t0, { httpStatus: status });
  });

  it("DB/API sync: score normalization", () => {
    const raw = 720;
    const normalized = normalizeScore(raw);
    expect(normalized).toBeGreaterThan(0);
    expect(normalized).toBeLessThanOrEqual(1000);
    const row = DB_API_SYNC_MATRIX.find((r) => r.db === "ScoringRun.totalScore");
    expect(row?.api).toBe("OnboardingStatusResponse.score");
  });

  it("F13–F14: Worker endpoints (optional live)", async () => {
    const workerUrl = process.env.AGRI_ACCESS_WORKER_URL;
    const workerToken = process.env.AGRI_ACCESS_WORKER_TOKEN ?? "dev-token";

    for (const id of ["F14", "F13"] as const) {
      const step = findStep(id);
      const t0 = Date.now();
      if (!workerUrl) {
        logStep(step, "skip", Date.now() - t0, { message: "AGRI_ACCESS_WORKER_URL not set" });
        continue;
      }
      try {
        if (id === "F14") {
          const res = await fetch(`${workerUrl}/api/model-status`);
          expect(res.ok).toBe(true);
          logStep(step, "pass", Date.now() - t0, { httpStatus: res.status });
        } else {
          const res = await fetch(`${workerUrl}/api/analyze`, {
            method: "POST",
            headers: {
              "Content-Type": "application/json",
              Authorization: `Bearer ${workerToken}`,
            },
            body: JSON.stringify({
              farmerName: "E2E Test",
              latitude: 40.38,
              longitude: 71.78,
              farmSize: 5.2,
              primaryCrop: "cotton",
              loanAmount: 50_000_000,
              loanTerm: 24,
              countryCode: "UZ",
              yearsFarming: 12,
              farmerAge: 42,
              consentCreditBureau: true,
            }),
          });
          expect(res.status).toBeLessThan(500);
          logStep(step, res.ok ? "pass" : "fail", Date.now() - t0, { httpStatus: res.status });
        }
      } catch (e) {
        logStep(step, "fail", Date.now() - t0, { message: String(e) });
      }
    }
  });

  afterAll(() => {
    const reportDir = resolve(process.cwd(), "reports");
    mkdirSync(reportDir, { recursive: true });
    const ts = new Date().toISOString().replace(/[:.]/g, "-");
    const passed = flowLog.filter((e) => e.status === "pass").length;
    const failed = flowLog.filter((e) => e.status === "fail").length;
    const skipped = flowLog.filter((e) => e.status === "skip").length;

    const report = {
      generatedAt: new Date().toISOString(),
      summary: { total: flowLog.length, passed, failed, skipped },
      functionalFlow: SYSTEM_FUNCTIONAL_FLOW,
      dbApiSync: DB_API_SYNC_MATRIX,
      executionLog: flowLog,
    };

    writeFileSync(resolve(reportDir, `e2e-api-flow-${ts}.json`), JSON.stringify(report, null, 2));

    const md = [
      "# AGNET API E2E Flow Report",
      "",
      `Generated: ${report.generatedAt}`,
      "",
      "## Summary",
      "",
      `| Metric | Count |`,
      `|--------|-------|`,
      `| Passed | ${passed} |`,
      `| Failed | ${failed} |`,
      `| Skipped | ${skipped} |`,
      "",
      "## Execution Log",
      "",
      "| Step | Phase | Method | Path | Status | ms |",
      "|------|-------|--------|------|--------|-----|",
      ...flowLog.map(
        (e) => `| ${e.stepId} | ${e.phase} | ${e.method} | ${e.path} | ${e.status} | ${e.durationMs} |`,
      ),
      "",
      "## DB ↔ API Sync Matrix",
      "",
      "| DB | API | Transform |",
      "|----|-----|-----------|",
      ...DB_API_SYNC_MATRIX.map((r) => `| ${r.db} | ${r.api} | ${r.transform} |`),
    ].join("\n");

    writeFileSync(resolve(reportDir, `e2e-api-flow-${ts}.md`), md);
    writeFileSync(resolve(reportDir, "e2e-api-flow-latest.md"), md);
    writeFileSync(resolve(reportDir, "e2e-api-flow-latest.json"), JSON.stringify(report, null, 2));
  });
});
