/**
 * EPFO assisted-verification coverage:
 *  - the EpfoService deterministic STUB contract the app relies on (no live
 *    portal): OTP 000000 → matched; UAN/OTP validation; session expiry.
 *  - the Next proxy routes' verify_bank_epf permission gate (§6) — only Accounts
 *    (or SuperUser) may reach the upstream service.
 * No EPFO_LIVE, no running service: the stub logic is imported directly and the
 * upstream fetch is mocked.
 */
import { vi, describe, it, expect, beforeEach } from "vitest";

vi.mock("@/auth", () => ({ auth: vi.fn() }));

import { auth } from "@/auth";
import { POST as otpPOST } from "@/app/api/epfo/otp/route";
import { POST as verifyPOST } from "@/app/api/epfo/route";
import { stubOtp, stubVerify, isUan, STUB_MATCH_OTP } from "../../../EpfoService/src/stub";
import { makeSession } from "./helpers";
import type { NextRequest } from "next/server";
import type { Role } from "@prisma/client";

const UAN = "100200300400";
const as = (role: Role | null) =>
  vi.mocked(auth as unknown as () => Promise<unknown>).mockResolvedValue(role ? makeSession(`u-${role}`, role) : null);

// Minimal NextRequest stand-in: the handlers only call req.json().
const req = (body: unknown) => ({ json: async () => body } as unknown as NextRequest);

beforeEach(() => vi.clearAllMocks());

describe("EpfoService stub contract", () => {
  it("stubOtp validates the 12-digit UAN and opens a session", () => {
    const ok = stubOtp(UAN, "sess-1");
    expect(ok.status).toBe(200);
    expect(ok.body).toMatchObject({ sessionId: "sess-1", stub: true });
    expect(stubOtp("123", "sess-1").status).toBe(400); // too short
    expect(stubOtp(undefined, "sess-1").status).toBe(400);
    expect(isUan(UAN)).toBe(true);
    expect(isUan("12345678901")).toBe(false);
  });

  it("stubVerify matches only OTP 000000 and validates session/uan/otp", () => {
    const session = { uan: UAN };
    const matched = stubVerify(session, UAN, STUB_MATCH_OTP);
    expect(matched.status).toBe(200);
    expect(matched.body).toMatchObject({ matched: true, name: "EPFO Member (stub)", status: "ACTIVE" });

    const wrong = stubVerify(session, UAN, "123456");
    expect(wrong.body).toMatchObject({ matched: false, name: null });

    expect(stubVerify(undefined, UAN, STUB_MATCH_OTP).status).toBe(410); // expired/unknown session
    expect(stubVerify(session, "999999999999", STUB_MATCH_OTP).status).toBe(400); // UAN mismatch
    expect(stubVerify(session, UAN, "12").status).toBe(400); // OTP too short
    expect(stubVerify(session, UAN, "abcd").status).toBe(400); // non-numeric OTP
  });
});

describe("EPFO proxy routes — verify_bank_epf gate (§6)", () => {
  it("rejects an unauthenticated caller (401) on both routes", async () => {
    as(null);
    expect((await otpPOST(req({ uan: UAN }))).status).toBe(401);
    expect((await verifyPOST(req({ sessionId: "s", uan: UAN, otp: STUB_MATCH_OTP }))).status).toBe(401);
  });

  it("forbids a role without verify_bank_epf (MPO → 403)", async () => {
    as("MANNING");
    expect((await otpPOST(req({ uan: UAN }))).status).toBe(403);
    expect((await verifyPOST(req({ sessionId: "s", uan: UAN, otp: STUB_MATCH_OTP }))).status).toBe(403);
  });

  it("lets Accounts through to the upstream service (mocked)", async () => {
    as("ACCOUNTS");
    const fetchMock = vi.spyOn(global, "fetch").mockResolvedValue(
      new Response(JSON.stringify({ sessionId: "epfo_1", mobileHint: "••••••••", stub: true }), {
        status: 200,
        headers: { "Content-Type": "application/json" },
      })
    );
    const res = await otpPOST(req({ uan: UAN }));
    expect(res.status).toBe(200);
    expect(await res.json()).toMatchObject({ sessionId: "epfo_1" });
    expect(fetchMock).toHaveBeenCalledOnce();
    fetchMock.mockRestore();
  });

  it("validates the body before calling upstream (Accounts, missing fields → 400)", async () => {
    as("ACCOUNTS");
    const fetchMock = vi.spyOn(global, "fetch");
    expect((await otpPOST(req({}))).status).toBe(400);
    expect((await verifyPOST(req({ uan: UAN }))).status).toBe(400); // no sessionId/otp
    expect(fetchMock).not.toHaveBeenCalled();
  });
});
