import { createHash } from 'node:crypto';
import { existsSync, readFileSync, readdirSync } from 'node:fs';
import { relative, resolve, sep } from 'node:path';
import { afterEach, describe, expect, it } from 'vitest';
import { GET as getContracts } from '../../../repos/theloopbreaker-site/app/api/v3/contracts/route';
import { GET as getContract } from '../../../repos/theloopbreaker-site/app/api/v3/contracts/[name]/route';
import { GET as getBondsByParty } from '../../../repos/theloopbreaker-site/app/api/v3/bonds/route';
import { GET as getBond } from '../../../repos/theloopbreaker-site/app/api/v3/bonds/[bondId]/route';
import { GET as getDispute } from '../../../repos/theloopbreaker-site/app/api/v3/disputes/[disputeId]/route';
import { GET as getEvidence } from '../../../repos/theloopbreaker-site/app/api/v3/evidence/[evidenceHash]/route';
import { GET as getTask } from '../../../repos/theloopbreaker-site/app/api/v3/tasks/[taskId]/route';
import { GET as getMandate } from '../../../repos/theloopbreaker-site/app/api/v3/mandates/[mandateHash]/route';
import { GET as getSingularMandate } from '../../../repos/theloopbreaker-site/app/api/v3/mandate/[mandateHash]/route';
import { GET as getProposal } from '../../../repos/theloopbreaker-site/app/api/v3/governance/proposals/[proposalId]/route';
import { GET as getPullCredit } from '../../../repos/theloopbreaker-site/app/api/v3/pull-credits/route';
import { GET as getMigration } from '../../../repos/theloopbreaker-site/app/api/v3/migration/dispositions/route';
import { GET as getChallenge } from '../../../repos/theloopbreaker-site/app/api/v3/x402/challenges/[challengeId]/route';
import { GET as getSolanaHealth } from '../../../repos/theloopbreaker-site/app/api/v3/solana/health/route';
import { assertV3UnavailableEnvelope } from '../../../repos/theloopbreaker-site/app/api/v3/_lib/http';
import { V3_API_ROUTES, assertV3ApiRouteInventory } from '../../../repos/theloopbreaker-site/app/api/v3/_lib/inventory';
import {
  V3_PATH_PARAMETER_SCHEMAS,
  V3_REQUEST_ID,
  V3_REQUEST_ID_EXAMPLE,
  isValidV3RequestId,
} from '../../../repos/theloopbreaker-site/app/api/v3/_lib/validation';
import { isAllowedV3ReleasePath, V3_X402_RELEASE_ENV, V3_X402_RELEASE_VALUE } from '../../../repos/theloopbreaker-site/app/api/v3/x402/_lib/release';
import generatedRegistry from '../../generated/route-registry.json' assert { type: 'json' };
import openapi from '../../generated/openapi.json' assert { type: 'json' };
import endpointCounts from '../../generated/endpoint-counts.json' assert { type: 'json' };
import sourceEvidence from '../../../baseline/schemas/v2/x402-action-input-schema-evidence.json' assert { type: 'json' };
import { validateSchema } from '../x402/json-schema-2020-subset';

const originalRelease = process.env[V3_X402_RELEASE_ENV];
const hash = `0x${'11'.repeat(32)}`;
const address = `0x${'22'.repeat(20)}`;
const request = (path: string) => new Request(`https://theloopbreaker.com${path}`);
const params = <T extends Record<string, string>>(value: T) => ({ params: value });

afterEach(() => {
  if (originalRelease === undefined) delete process.env[V3_X402_RELEASE_ENV];
  else process.env[V3_X402_RELEASE_ENV] = originalRelease;
});

describe('coherent fail-closed V3 read-only catalog', () => {
  it('keeps every route explicitly V3, read-only, unique, and dispositioned', () => {
    assertV3ApiRouteInventory(V3_API_ROUTES);
    expect(V3_API_ROUTES).toHaveLength(20);
    expect(V3_API_ROUTES.every((route) => route.path.startsWith('/api/v3/') && route.readOnly)).toBe(true);
    expect(new Set(V3_API_ROUTES.map((route) => `${route.method} ${route.path}`)).size).toBe(20);
    expect(V3_API_ROUTES.find((route) => route.id === 'mandate')?.path).toContain('/mandates/');
    expect(V3_API_ROUTES.find((route) => route.id === 'mandate-compat')?.description).toContain('never falls back to V2');
  });

  it('generates the clickable receipt example from the shared valid request ID fixture', () => {
    const receiptRoute = V3_API_ROUTES.find((route) => route.id === 'x402-receipt');
    expect(receiptRoute).toBeDefined();
    expect(V3_REQUEST_ID_EXAMPLE.length).toBeGreaterThanOrEqual(32);
    expect(V3_REQUEST_ID_EXAMPLE.length).toBeLessThanOrEqual(160);
    expect(V3_REQUEST_ID.test(V3_REQUEST_ID_EXAMPLE)).toBe(true);
    const concrete = receiptRoute!.path.replace(':requestId', V3_REQUEST_ID_EXAMPLE);
    expect(concrete).not.toContain(':');
    expect(isAllowedV3ReleasePath(concrete)).toBe(true);
    const page = readFileSync(resolve(import.meta.dirname, '../../../repos/theloopbreaker-site/app/v3/page.tsx'), 'utf8');
    expect(page).toContain("import { V3_REQUEST_ID_EXAMPLE } from '../api/v3/_lib/validation'");
    expect(page).toContain(".replace(':requestId', V3_REQUEST_ID_EXAMPLE)");
    expect(page).not.toContain('request-id-0001');
  });

  it('serves a schema-validated catalog with null addresses and truthful dispositions', async () => {
    process.env[V3_X402_RELEASE_ENV] = V3_X402_RELEASE_VALUE;
    const response = getContracts();
    expect(response.status).toBe(200);
    const body = await response.json();
    expect(body).toMatchObject({
      schema: 'vaultfire.v3.contract-catalog.v1',
      protocolVersion: '3.0.0',
      deploymentState: 'unconfigured',
      addressPolicy: 'null-until-independently-verified',
    });
    expect(body.contracts.every((contract: any) => contract.address === null && contract.executableEvidenceAvailable === false)).toBe(true);
    expect(body.contracts.find((contract: any) => contract.name === 'TaskEscrowV3')?.disposition).toBe('deferred');
    expect(body.contracts.find((contract: any) => contract.name === 'FlourishingMetricsOracleV3')?.disposition).toBe('retired');
    expect((await getContract(request('/api/v3/contracts/MultisigGovernanceV3'), params({ name: 'MultisigGovernanceV3' }))).status).toBe(200);
    expect((await getContract(request('/api/v3/contracts/NotV3'), params({ name: 'NotV3' }))).status).toBe(404);
  });

  it('validates identifiers and returns canonical typed unconfigured/degraded envelopes', async () => {
    process.env[V3_X402_RELEASE_ENV] = V3_X402_RELEASE_VALUE;
    const responses = [
      getBondsByParty(request(`/api/v3/bonds?party=${address}`)),
      await getBond(request('/api/v3/bonds/1'), params({ bondId: '1' })),
      await getDispute(request('/api/v3/disputes/1'), params({ disputeId: '1' })),
      await getEvidence(request(`/api/v3/evidence/${hash}`), params({ evidenceHash: hash })),
      await getTask(request('/api/v3/tasks/1'), params({ taskId: '1' })),
      await getMandate(request(`/api/v3/mandates/${hash}`), params({ mandateHash: hash })),
      await getProposal(request('/api/v3/governance/proposals/1'), params({ proposalId: '1' })),
      getPullCredit(request(`/api/v3/pull-credits?contract=${address}&asset=${address}&holder=${address}`)),
      await getChallenge(request(`/api/v3/x402/challenges/${hash}`), params({ challengeId: hash })),
    ];
    for (const response of responses) {
      expect(response.status).toBe(503);
      const body = await response.json();
      expect(() => assertV3UnavailableEnvelope(body)).not.toThrow();
      expect(body).toMatchObject({ data: null, evidence: { deploymentVerified: false, adapterConfigured: false, rpcContacted: false, v2FallbackUsed: false } });
    }
    expect((await getBond(request('/api/v3/bonds/nope'), params({ bondId: 'nope' }))).status).toBe(400);
    expect((await getMandate(request('/api/v3/mandates/nope'), params({ mandateHash: 'nope' }))).status).toBe(400);
    expect(getBondsByParty(request(`/api/v3/bonds?party=${address}&unknown=1`)).status).toBe(400);
    expect(getBondsByParty(request(`/api/v3/bonds?party=${address}&party=${address}`)).status).toBe(400);
    expect(getBondsByParty(request(`/api/v3/bonds?party=${address.toUpperCase()}`)).status).toBe(400);
    expect(getPullCredit(request(`/api/v3/pull-credits?contract=${address}&asset=${address}&holder=${address}&holder=${address}`)).status).toBe(400);
  });

  it('makes the singular mandate route only a compatibility wrapper over plural V3', async () => {
    process.env[V3_X402_RELEASE_ENV] = V3_X402_RELEASE_VALUE;
    const plural = await getMandate(request(`/api/v3/mandates/${hash}`), params({ mandateHash: hash }));
    const singular = await getSingularMandate(request(`/api/v3/mandate/${hash}`), params({ mandateHash: hash }));
    const singularBody = await singular.json();
    const pluralBody = await plural.json();
    delete singularBody.observedAt;
    delete pluralBody.observedAt;
    expect(singularBody).toEqual(pluralBody);
    const source = readFileSync(resolve(import.meta.dirname, '../../../repos/theloopbreaker-site/app/api/v3/mandate/[mandateHash]/route.ts'), 'utf8');
    expect(source).toContain('../../mandates/[mandateHash]/route');
    expect(source).not.toMatch(/\/api\/(?:x402|v2)|verify-payment|tx-encode/);
  });

  it('reports SDK, pilot, decision-input, x402, and Solana dispositions without executable claims', async () => {
    process.env[V3_X402_RELEASE_ENV] = V3_X402_RELEASE_VALUE;
    const migration = await (await getMigration()).json();
    expect(migration.canonicalSdk).toMatchObject({
      supported: ['bonds', 'pullCredits', 'solvency', 'mandates', 'capabilities', 'taskEscrow', 'governance'],
      unavailable: ['x402.status', 'x402.discovery', 'x402.challenge', 'x402.receipt'],
      retired: ['latest', 'v2-fallback'],
    });
    expect(migration.policy).toMatchObject({ v2Fallback: 'forbidden', writesEnabled: false, paymentsEnabled: false });
    expect(migration.policy.prohibitedDecisionInputs).toContain('belief');
    const solana = await (await getSolanaHealth()).json();
    expect(solana).toMatchObject({
      readiness: 'source-only',
      operational: false,
      source: { supportedCandidateCount: 6, redesignRequiredCount: 4, retiredCount: 5 },
      executable: { evidenceAvailable: false, deploymentAuthorized: false, deploymentMapAvailable: false, programAddressVerified: false },
      rpcContacted: false,
      protectedDecisionUse: 'prohibited',
    });
  });

  it('contains no V2 imports, deployment addresses, RPC calls, wallets, or write enablement in V3 route sources', () => {
    const root = resolve(import.meta.dirname, '../../../repos/theloopbreaker-site/app/api/v3');
    const files = (directory: string): string[] => readdirSync(directory, { withFileTypes: true }).flatMap((entry) => {
      const path = resolve(directory, entry.name);
      return entry.isDirectory() ? files(path) : path.endsWith('.ts') ? [path] : [];
    });
    for (const file of files(root)) {
      const source = readFileSync(file, 'utf8');
      expect(source, file).not.toMatch(/from ['"][^'"]*\/api\/(?:x402|agent|contracts)\//);
      expect(source, file).not.toMatch(/createPublicClient|createWalletClient|JsonRpcProvider|eth_call|sendTransaction\(/);
      expect(source, file).not.toMatch(/0x[a-fA-F0-9]{40}/);
    }
  });

  it('prevents filesystem, inventory, release gate, middleware, schemas, docs, registry, and OpenAPI drift', () => {
    const site = resolve(import.meta.dirname, '../../../repos/theloopbreaker-site');
    const apiRoot = resolve(site, 'app/api/v3');
    const walk = (directory: string): string[] => readdirSync(directory, { withFileTypes: true }).flatMap((entry) => {
      const path = resolve(directory, entry.name);
      return entry.isDirectory() ? walk(path) : entry.name === 'route.ts' ? [path] : [];
    });
    const filesystem = walk(apiRoot).flatMap((file) => {
      const path = `/api/v3/${relative(apiRoot, file).split(sep).slice(0, -1).map((segment) => segment.replace(/^\[([^\]]+)\]$/, ':$1')).join('/')}`;
      const source = readFileSync(file, 'utf8');
      return [...source.matchAll(/export (?:async )?function (GET|POST)\b/g)].map((match) => `${match[1]} ${path}`);
    }).sort();
    const inventory = V3_API_ROUTES.map((route) => `${route.method} ${route.path}`).sort();
    expect(filesystem).toEqual(inventory);

    const generated = generatedRegistry.routes.filter((route) => route.version === 'v3');
    expect(generated.map((route) => `${route.method} ${route.path}`).sort()).toEqual(inventory);
    expect(generated).toHaveLength(20);
    expect(endpointCounts.byVersion.v3).toBe(20);
    const singular = generated.find((route) => route.id === 'mandate-compat');
    expect(singular?.responseSchema).toContain('read-result.v1.schema.json');
    expect(singular?.responseContracts).toEqual({
      '400': 'https://theloopbreaker.com/schemas/v3-x402/input-error.v1.schema.json',
      '503': 'https://theloopbreaker.com/schemas/v3-x402/read-result.v1.schema.json|https://theloopbreaker.com/schemas/v3-x402/release-gate.v1.schema.json',
    });

    const openapiV3 = Object.entries(openapi.paths).flatMap(([path, operations]) =>
      Object.entries(operations as Record<string, any>).map(([method, operation]) => ({
        identity: `${method.toUpperCase()} ${path.replace(/\{([^}]+)\}/g, ':$1')}`,
        operation,
      })),
    ).filter(({ identity }) => identity.includes(' /api/v3/'));
    expect(openapiV3.map(({ identity }) => identity).sort()).toEqual(inventory);
    for (const { operation } of openapiV3) {
      expect(operation.responses['402']).toBeUndefined();
      for (const response of Object.values<any>(operation.responses)) {
        const schema = response.content?.['application/json']?.schema;
        const refs = schema?.oneOf?.map((candidate: any) => candidate.$ref) ?? [schema?.$ref];
        expect(refs.every((ref: unknown) => typeof ref === 'string')).toBe(true);
        for (const ref of refs) {
          expect(existsSync(resolve(import.meta.dirname, '../../schemas/v3-x402', ref.split('/').pop()))).toBe(true);
        }
      }
    }

    const concrete = (path: string) => path
      .replace(':name', 'MultisigGovernanceV3').replace(/:(?:bondId|disputeId|taskId|proposalId)/, '1')
      .replace(/:(?:mandateHash|evidenceHash|challengeId)/, `0x${'1'.repeat(64)}`)
      .replace(':requestId', 'v3r_89abcdef0123456789abcdef01234567');
    for (const route of V3_API_ROUTES) expect(isAllowedV3ReleasePath(concrete(route.path)), route.path).toBe(true);
    const middleware = readFileSync(resolve(site, 'middleware.ts'), 'utf8');
    expect(middleware).toContain("'/api/v3/:path*'");
    expect(middleware).not.toContain("'/api/v3/x402/:path*'");
    const page = readFileSync(resolve(site, 'app/v3/page.tsx'), 'utf8');
    const nav = readFileSync(resolve(site, 'app/components/Nav.tsx'), 'utf8');
    expect(page).toContain('V3_API_ROUTES');
    expect(page).toContain('V3 · READ ONLY');
    expect(nav).toContain('href="/v3"');
    expect(nav).toContain('V3 API');
  });

  it('keeps every OpenAPI V3 path parameter schema identical to the runtime constraint source', () => {
    let parameterCount = 0;
    for (const route of V3_API_ROUTES) {
      const parameterNames = [...route.path.matchAll(/:([A-Za-z][A-Za-z0-9_]*)/g)].map((match) => match[1]);
      const openApiPath = route.path.replace(/:([A-Za-z][A-Za-z0-9_]*)/g, '{$1}');
      const operation = (openapi.paths as Record<string, Record<string, any>>)[openApiPath][route.method.toLowerCase()];
      const pathParameters = (operation.parameters ?? []).filter((parameter: any) => parameter.in === 'path');
      expect(pathParameters.map((parameter: any) => parameter.name), route.id).toEqual(parameterNames);
      for (const parameter of pathParameters) {
        expect(parameter.required, `${route.id}:${parameter.name}`).toBe(true);
        expect(parameter.schema, `${route.id}:${parameter.name}`).toEqual(
          V3_PATH_PARAMETER_SCHEMAS[parameter.name as keyof typeof V3_PATH_PARAMETER_SCHEMAS],
        );
        expect(() => new RegExp(parameter.schema.pattern)).not.toThrow();
        parameterCount += 1;
      }
    }
    expect(parameterCount).toBe(10);
  });

  it('keeps standard OpenAPI request-ID validation identical to runtime over positive and negative corpora', () => {
    const operation = (openapi.paths as Record<string, Record<string, any>>)
      ['/api/v3/x402/receipts/{requestId}'].get;
    const requestIdSchema = operation.parameters.find(
      (parameter: any) => parameter.in === 'path' && parameter.name === 'requestId',
    ).schema;
    const positives = [
      V3_REQUEST_ID_EXAMPLE,
      'v3r_fedcba98765432100123456789abcdef',
      'v3r_89abcdef0123456776543210fedcba98',
      'v3r_00112233445566778899aabbccddeeff',
    ];
    const negatives = [
      `v3r_${'a'.repeat(32)}`,
      `v3r_${'ab'.repeat(16)}`,
      `v3r_${'abcd'.repeat(8)}`,
      `v3r_${'01234567'.repeat(4)}`,
      'v3r_00001111222233334444555566660000',
      'v3r_0123456789abcdef0123456789abcde',
      'v3r_0123456789ABCDEF0123456789ABCDEF',
      'request-release-path-0123456789abcdef',
    ];
    for (const value of [...positives, ...negatives]) {
      const standardSchemaAccepts = validateSchema(requestIdSchema, value).length === 0;
      expect(standardSchemaAccepts, value).toBe(isValidV3RequestId(value));
    }
    expect(positives.every(isValidV3RequestId)).toBe(true);
    expect(negatives.every((value) => !isValidV3RequestId(value))).toBe(true);
  });

  it('preserves protected V2 action hashes', () => {
    expect(existsSync(resolve(import.meta.dirname, '../../../repos/theloopbreaker-site/app/api/x402'))).toBe(true);
    for (const entry of sourceEvidence.entries) {
      const bytes = readFileSync(resolve(import.meta.dirname, '../../..', entry.source));
      expect(createHash('sha256').update(bytes).digest('hex'), entry.source).toBe(entry.sha256);
    }
  });
});
