import { afterAll, beforeAll, expect, it } from 'vitest';
import { createHmac, timingSafeEqual } from 'node:crypto';

import { createAdapter } from './src/adapters.js';
import { apiRequest, field } from './src/client.js';
import { loadCredentials, type Credentials } from './src/credentials.js';
import { CAPABILITIES, type CapabilityDefinition } from './src/manifest.js';
import { BlockedOnInfraError, NotImplementedError, blockedOnInfra, notImplemented, record, resetOutcomes, writeGapReports } from './src/outcomes.js';
import { parseHarnessOptions } from './src/options.js';
import { LegacyFixtureSeeder } from './src/legacy-fixtures.js';

const options = parseHarnessOptions(process.argv, process.env);
const adapter = createAdapter(options.adapter, options.baseUrl);
let credentials: Credentials;
let legacyFixtures: LegacyFixtureSeeder | undefined;

resetOutcomes();
beforeAll(() => {
  credentials = loadCredentials(options.adapter, process.env);
});

afterAll(async () => {
  await legacyFixtures?.cleanup();
  await writeGapReports(options.adapter, options.baseUrl);
});

async function fixtureSeeder(): Promise<LegacyFixtureSeeder> {
  if (!legacyFixtures) {
    const candidate = new LegacyFixtureSeeder(process.env);
    await candidate.initialize();
    legacyFixtures = candidate;
  }
  return legacyFixtures;
}

function validCredential(): string {
  if (!credentials.valid) throw new Error(`${options.adapter} valid credential configuration is required`);
  return credentials.valid;
}

function required(value: string | undefined, name: string): string {
  if (!value) throw new Error(`${name} configuration is required`);
  return value;
}

function numericBalance(body: unknown): number {
  if (body && typeof body === 'object') {
    const object = body as Record<string, unknown>;
    for (const key of ['remaining', 'balance', 'available', 'units_remaining', 'credits_balance']) {
      if (typeof object[key] === 'number') return object[key];
    }
    for (const value of Object.values(object)) {
      try {
        return numericBalance(value);
      } catch {
        continue;
      }
    }
  }
  throw new Error('Response contains no numeric wallet balance');
}

async function waitForRetry(response: Awaited<ReturnType<typeof apiRequest>>): Promise<void> {
  const raw = response.headers.get('retry-after');
  expect(raw).toBeTruthy();
  const seconds = Number(raw);
  const delay = Number.isFinite(seconds) ? seconds * 1_000 : Math.max(0, Date.parse(raw!) - Date.now());
  await new Promise((resolve) => setTimeout(resolve, Math.min(delay, 60_000)));
}

function title(capability: CapabilityDefinition): string {
  const smoke = capability.smoke ? ' @smoke' : '';
  return `[${capability.id}][${capability.gates.join(',')}]${smoke} ${capability.name}`;
}

async function waitForCallback(inspectUrl: string): Promise<{ rawBody: string; signature: string; timestamp: string; attempts: number }> {
  const deadline = Date.now() + 60_000;
  while (Date.now() < deadline) {
    const response = await fetch(inspectUrl);
    const capture = await response.json() as { rawBody?: string; signature?: string; timestamp?: string; attempts?: number } | null;
    if (capture?.rawBody && capture.signature) {
      return capture as { rawBody: string; signature: string; timestamp: string; attempts: number };
    }
    await new Promise((resolve) => setTimeout(resolve, 1_000));
  }
  throw new Error('Callback collector did not receive a signed callback within 60 seconds');
}

async function waitForCompletedJob(jobId: string): Promise<Record<string, unknown>> {
  const deadline = Date.now() + 60_000;
  while (Date.now() < deadline) {
    const response = await apiRequest(adapter, 'GET', 'job', validCredential(), undefined, jobId);
    expect(response.status).toBe(200);
    const data = adapter.unwrap(response.body);
    const status = field(data, 'status');
    if (status === 'completed') return data;
    if (status === 'failed') throw new Error(`Async job failed: ${String(field(data, 'errorMessage', 'error_message') ?? 'unknown error')}`);
    await new Promise((resolve) => setTimeout(resolve, 1_000));
  }
  throw new Error('Async job did not complete within 60 seconds');
}

function capabilityTest(capability: CapabilityDefinition, run: () => Promise<void>): void {
  if (capability.droppedReason) {
    record({ capability: capability.id, gates: capability.gates, status: 'DROPPED', reason: capability.droppedReason });
    it.skip(`${title(capability)} — DROPPED: ${capability.droppedReason}`, () => {});
    return;
  }
  it(title(capability), async (context) => {
    try {
      await run();
      record({ capability: capability.id, gates: capability.gates, status: 'PASS' });
    } catch (error) {
      if (error instanceof NotImplementedError) {
        record({ capability: capability.id, gates: capability.gates, status: 'NOT-IMPLEMENTED', reason: error.message });
        context.skip(error.message);
        return;
      }
      if (error instanceof BlockedOnInfraError) {
        record({ capability: capability.id, gates: capability.gates, status: 'BLOCKED-ON-INFRA', reason: error.message });
        context.skip(error.message);
        return;
      }
      record({ capability: capability.id, gates: capability.gates, status: 'FAIL', reason: error instanceof Error ? error.message : String(error) });
      throw error;
    }
  });
}

const byId = Object.fromEntries(CAPABILITIES.map((capability) => [capability.id, capability]));
const translationBody = { source_lang: 'en', target_lang: 'he', tone: 'neutral', content: 'Capability oracle' };

capabilityTest(byId['CAP-01'], async () => {
  const response = await apiRequest(adapter, 'POST', 'translate', validCredential(), translationBody);
  expect(response.status).toBe(200);
  const data = adapter.unwrap(response.body);
  expect(field(data, 'translation')).toBeTypeOf('string');
  expect(field(data, 'characters_used', 'charactersUsed')).toBe(translationBody.content.length);
});

capabilityTest(byId['CAP-02'], async () => {
  const clientJobId = `wp_${Date.now()}`;
  const response = await apiRequest(adapter, 'POST', 'jobs', validCredential(), {
    job_id: clientJobId, source_lang: 'en', target_lang: 'he', content: 'Capability async oracle',
    callback_url: process.env.CAPABILITY_CALLBACK_URL, callback_secret: process.env.CAPABILITY_CALLBACK_SECRET,
  });
  expect([200, 202]).toContain(response.status);
  const data = adapter.unwrap(response.body);
  const jobId = field(data, 'jobId', 'job_id');
  expect(jobId).toBeTypeOf('string');
  const completed = await waitForCompletedJob(String(jobId));
  expect(field(completed, 'translation')).toBeTypeOf('string');
  expect(field(completed, 'charactersUsed', 'characters_used')).toBeTypeOf('number');
  if (!process.env.CAPABILITY_CALLBACK_URL || !process.env.CAPABILITY_CALLBACK_SECRET) {
    blockedOnInfra('Callback delivery requires public collector. Run `npm run capability:callback-collector`, expose `http://127.0.0.1:8788` with `cloudflared tunnel --url http://127.0.0.1:8788`, then set CAPABILITY_CALLBACK_URL=<tunnel>/callback, CAPABILITY_CALLBACK_INSPECT_URL=http://127.0.0.1:8788/capture, and CAPABILITY_CALLBACK_SECRET. Job submission and status polling passed; MR-04 and MR-08 remain unearned.');
  }
  const inspectUrl = required(process.env.CAPABILITY_CALLBACK_INSPECT_URL, 'CAPABILITY_CALLBACK_INSPECT_URL');
  const capture = await waitForCallback(inspectUrl);
  const expectedSignature = createHmac('sha256', process.env.CAPABILITY_CALLBACK_SECRET).update(capture.rawBody).digest('hex');
  const actualSignature = capture.signature.replace(/^sha256=/, '');
  expect(actualSignature).toHaveLength(expectedSignature.length);
  expect(timingSafeEqual(Buffer.from(actualSignature), Buffer.from(expectedSignature))).toBe(true);
  expect(Math.abs(Date.now() - Number(capture.timestamp))).toBeLessThanOrEqual(300_000);
  const payload = JSON.parse(capture.rawBody) as Record<string, unknown>;
  expect(field(payload, 'job_id', 'jobId')).toBeTypeOf('string');
  expect(field(payload, 'status')).toBe('completed');
  expect(field(payload, 'clientJobId', 'client_job_id')).toBe(clientJobId);
  expect(field(payload, 'translation')).toBeTypeOf('string');
  expect(field(payload, 'characters_used', 'charactersUsed')).toBeTypeOf('number');
  expect(capture.attempts).toBeGreaterThanOrEqual(1);
  if (options.adapter === 'legacy') {
    record({
      capability: 'CAP-02', gates: byId['CAP-02'].gates, status: 'PASS',
      reason: 'Primary callback delivery and HMAC signature pass; retry sub-assertion NOT-IMPLEMENTED because legacy never invokes retryFailedWebhook',
    });
  }
});

capabilityTest(byId['CAP-03'], async () => {
  const response = await apiRequest(adapter, 'POST', 'estimate', validCredential(), {
    content: 'Capability estimate', source_lang: 'en', target_langs: ['he'],
  });
  expect(response.status).toBe(200);
  const data = adapter.unwrap(response.body);
  expect(field(data, 'estimates')).toBeTypeOf('object');
});

capabilityTest(byId['CAP-04'], async () => {
  const response = await apiRequest(adapter, 'GET', 'exceptions', validCredential());
  expect(response.status).toBe(200);
  const push = await apiRequest(adapter, 'POST', 'exceptionsSync', validCredential(), { exceptions: [] });
  expect(push.status).toBe(200);
});

capabilityTest(byId['CAP-05'], async () => {
  if (options.adapter === 'legacy') notImplemented('Legacy POST /v1/sites/register has documented 500 old-prod defect');
  const capped = required(credentials.seatCapped, 'PLATFORM_SEAT_CAPPED_API_KEY');
  const response = await apiRequest(adapter, 'POST', 'sites', capped, { site_url: `https://cap-${Date.now()}.invalid`, plugin: 'international' });
  expect(response.status).toBe(409);
  expect(adapter.errorCode(response.body)).toBe('SITE_LIMIT_REACHED');
});

capabilityTest(byId['CAP-06'], async () => {
  if (options.adapter === 'legacy') notImplemented('Site heartbeat depends on documented broken legacy registration route');
  const siteId = required(process.env.PLATFORM_SITE_ID, 'PLATFORM_SITE_ID');
  expect((await apiRequest(adapter, 'PATCH', 'site', validCredential(), { active: true }, siteId)).status).toBe(200);
  expect((await apiRequest(adapter, 'PATCH', 'site', validCredential(), { active: false }, siteId)).status).toBe(200);
  expect((await apiRequest(adapter, 'PATCH', 'site', validCredential(), { active: false }, siteId)).status).toBe(200);
});

capabilityTest(byId['CAP-07'], async () => {
  const response = await apiRequest(adapter, 'GET', 'plans');
  expect(response.status).toBe(200);
  expect(field(adapter.unwrap(response.body), 'plans')).toBeInstanceOf(Array);
});

capabilityTest(byId['CAP-08'], async () => {});
capabilityTest(byId['CAP-09'], async () => {});

capabilityTest(byId['CAP-10'], async () => {
  if (options.adapter === 'legacy') notImplemented('MR-10 chargeKey webhook idempotency has no legacy plugin-contract surface');
  const session = required(credentials.session, 'PLATFORM_SESSION_TOKEN');
  const response = await apiRequest(adapter, 'POST', 'subscribe', session, { plugin: 'international' });
  expect([200, 201, 202]).toContain(response.status);
  const webhookUrl = required(process.env.PLATFORM_PAYPAL_WEBHOOK_URL, 'PLATFORM_PAYPAL_WEBHOOK_URL');
  const event = JSON.parse(required(process.env.PLATFORM_PAYPAL_WEBHOOK_EVENT, 'PLATFORM_PAYPAL_WEBHOOK_EVENT')) as Record<string, unknown>;
  const accountBefore = await apiRequest(adapter, 'GET', 'account', session);
  const before = numericBalance(accountBefore.body);
  for (const id of [`cap-${Date.now()}-1`, `cap-${Date.now()}-2`]) {
    const webhook = await fetch(webhookUrl, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ ...event, id }) });
    expect([200, 202, 204]).toContain(webhook.status);
  }
  const after = numericBalance((await apiRequest(adapter, 'GET', 'account', session)).body);
  expect(after - before).toBe(Number(required(process.env.PLATFORM_PAYPAL_CREDIT_GRANT, 'PLATFORM_PAYPAL_CREDIT_GRANT')));
});

capabilityTest(byId['CAP-11'], async () => {});

capabilityTest(byId['CAP-12'], async () => {
  if (options.adapter === 'legacy') notImplemented('Stored ipz_license_key has documented incompatible legacy format');
  const license = required(credentials.license, 'PLATFORM_LICENSE_KEY');
  const activate = await apiRequest(adapter, 'POST', 'licenseActivate', undefined, { license_key: license });
  expect(activate.status).toBe(200);
  expect((await apiRequest(adapter, 'POST', 'licenseValidate', undefined, { license_key: license })).status).toBe(200);
  expect((await apiRequest(adapter, 'POST', 'licenseDeactivate', undefined, { license_key: license })).status).toBe(200);
  expect((await apiRequest(adapter, 'POST', 'licenseDeactivate', undefined, { license_key: license })).status).toBe(200);
  expect((await apiRequest(adapter, 'POST', 'licenseActivate', undefined, { license_key: license })).status).toBe(200);
  expect((await apiRequest(adapter, 'GET', 'licenseStatus', undefined, undefined, license)).status).toBe(200);
});

capabilityTest(byId['CAP-13'], async () => {
  if (options.adapter === 'legacy') notImplemented('Legacy GET /v1/international/updates/check route is documented absent');
  const response = await apiRequest(adapter, 'GET', 'updates');
  expect(response.status).toBe(200);
  const data = adapter.unwrap(response.body);
  expect(field(data, 'update_available')).toBeTypeOf('boolean');
  expect(field(data, 'version')).toBeTypeOf('string');
  expect(field(data, 'url')).toBeTypeOf('string');
});

capabilityTest(byId['CAP-14'], async () => {
  expect((await apiRequest(adapter, 'GET', 'health')).status).toBe(200);
});

capabilityTest(byId['CAP-15'], async () => {
  const accepted = await apiRequest(adapter, 'POST', 'translate', validCredential(), translationBody);
  expect(accepted.status).toBe(200);
  const rejected = await apiRequest(adapter, 'POST', 'translate', 'invalid.capability-credential', translationBody);
  expect(rejected.status).toBe(401);
});

capabilityTest(byId['CAP-16'], async () => {
  let revoked = credentials.revoked;
  let wrongPlugin = credentials.wrongPlugin;
  if (options.adapter === 'legacy') {
    const seeder = await fixtureSeeder();
    const revokedFixture = await seeder.seedCredential('international', 64, 'revoked');
    await seeder.revoke(revokedFixture);
    revoked = required(revokedFixture.apiKey, 'seeded revoked credential');
    wrongPlugin = required((await seeder.seedCredential('multilingual', 64, 'wrong-plugin')).apiKey, 'seeded wrong-plugin credential');
  }
  revoked = required(revoked, options.adapter === 'legacy' ? 'seeded revoked credential' : 'PLATFORM_REVOKED_API_KEY');
  wrongPlugin = required(wrongPlugin, options.adapter === 'legacy' ? 'seeded wrong-plugin credential' : 'PLATFORM_WRONG_PLUGIN_API_KEY');
  expect((await apiRequest(adapter, 'POST', 'translate', revoked, translationBody)).status).toBe(401);
  const wrongPluginStatus = (await apiRequest(adapter, 'POST', 'translate', wrongPlugin, translationBody)).status;
  if (options.adapter === 'legacy' && wrongPluginStatus === 200) {
    notImplemented('Legacy revocation passes, but plugin entitlement is absent: an international request falls back to any active subscription');
  }
  expect([401, 403]).toContain(wrongPluginStatus);
});

capabilityTest(byId['CAP-17'], async () => {
  const meteredCredential = options.adapter === 'legacy'
    ? required((await (await fixtureSeeder()).seedCredential('international', translationBody.content.length + 1, 'low-credit')).apiKey, 'seeded low-credit credential')
    : validCredential();
  const beforeResponse = await apiRequest(adapter, 'GET', 'account', meteredCredential);
  expect(beforeResponse.status).toBe(200);
  const accountBefore = numericBalance(beforeResponse.body);
  const translated = await apiRequest(adapter, 'POST', 'translate', meteredCredential, translationBody);
  expect(translated.status).toBe(200);
  const afterResponse = await apiRequest(adapter, 'GET', 'account', meteredCredential);
  expect(afterResponse.status).toBe(200);
  const accountAfter = numericBalance(afterResponse.body);
  expect(accountBefore - accountAfter).toBe(translationBody.content.length);
  const lowCredit = options.adapter === 'legacy' ? meteredCredential : required(credentials.insufficientCredits, 'PLATFORM_INSUFFICIENT_CREDITS_API_KEY');
  const response = await apiRequest(adapter, 'POST', 'translate', lowCredit, { ...translationBody, content: 'xx' });
  expect(response.status).toBe(402);
  expect(adapter.errorCode(response.body)).toBe('INSUFFICIENT_CREDITS');
  if (options.adapter === 'legacy') return;
  const forum = required(credentials.forum, 'PLATFORM_FORUM_API_KEY');
  const forumResult = await apiRequest(adapter, 'POST', 'plugin', forum, { units: 1, content: 'forum capability' }, 'forum');
  expect(forumResult.status).toBe(200);
  const forumLow = required(credentials.forumInsufficientCredits, 'PLATFORM_FORUM_INSUFFICIENT_CREDITS_API_KEY');
  const forumBlocked = await apiRequest(adapter, 'POST', 'plugin', forumLow, { units: 1, content: 'forum capability' }, 'forum');
  expect(forumBlocked.status).toBe(402);
  expect(adapter.errorCode(forumBlocked.body)).toBe('INSUFFICIENT_CREDITS');
  const staff = required(credentials.staff, 'PLATFORM_STAFF_TOKEN');
  const ledger = await apiRequest(adapter, 'GET', 'staffLedger', staff);
  expect(ledger.status).toBe(200);
  expect(JSON.stringify(ledger.body)).toContain('forum');
});

capabilityTest(byId['CAP-18'], async () => {
  if (options.adapter === 'legacy') notImplemented('Legacy production has no rate limiting');
  const burst = Number(process.env.CAPABILITY_RATE_BURST ?? 70);
  const responses = await Promise.all(Array.from({ length: burst }, () => apiRequest(adapter, 'GET', 'health')));
  const throttled = responses.find((response) => response.status === 429);
  expect(throttled, `Expected at least one 429 from burst of ${burst}`).toBeDefined();
  expect(throttled?.headers.get('retry-after')).toBeTruthy();

  const credentialBurst = await Promise.all(Array.from({ length: burst }, () => apiRequest(adapter, 'POST', 'translate', validCredential(), translationBody)));
  const credentialThrottle = credentialBurst.find((response) => response.status === 429);
  expect(credentialThrottle, `Expected credential throttle from burst of ${burst}`).toBeDefined();
  await waitForRetry(credentialThrottle!);
  expect((await apiRequest(adapter, 'POST', 'translate', validCredential(), translationBody)).status).toBe(200);

  const loginResponses = [];
  for (let attempt = 0; attempt < 8; attempt += 1) {
    loginResponses.push(await apiRequest(adapter, 'POST', 'login', undefined, { email: 'capability-invalid@example.invalid', password: 'invalid' }));
  }
  expect(loginResponses.some((response) => response.status === 429)).toBe(true);

  const bulkCount = Number(process.env.CAPABILITY_BULK_COUNT ?? 200);
  for (let index = 0; index < bulkCount; index += 1) {
    const response = await apiRequest(adapter, 'POST', 'jobs', validCredential(), {
      job_id: `wp_${Date.now()}_${index}`, source_lang: 'en', target_lang: 'he', content: `Bulk ${index}`,
    });
    if (response.status === 429) {
      await waitForRetry(response);
      const retried = await apiRequest(adapter, 'POST', 'jobs', validCredential(), {
        job_id: `wp_${Date.now()}_${index}_retry`, source_lang: 'en', target_lang: 'he', content: `Bulk ${index}`,
      });
      expect([200, 202]).toContain(retried.status);
    } else {
      expect([200, 202]).toContain(response.status);
    }
  }
});

expect(CAPABILITIES).toHaveLength(18);
