/**
 * audit-dev-wire / audit.test.ts — Unit + fixture-driven tests of the parity
 * audit. Verifies the 3 main cases the user originally hit:
 *
 *   1. Frontend calls /api/screens/demandes/list, backend has the matching
 *      [HttpGet("list")] → no findings (the happy path the CLI now produces).
 *   2. Frontend calls /api/affaires/accueil/dashboard (orphan URL from the
 *      pre-Wave-1 apiBasePath template) → DEV-WIRE-001 err.
 *   3. Backend has a route nobody calls → DEV-WIRE-001 warn (informational).
 */
import { describe, it, expect, beforeAll, afterAll } from 'vitest'
import { mkdir, writeFile, rm } from 'node:fs/promises'
import path from 'node:path'
import os from 'node:os'
import { audit } from '../audit.js'

let tmpRoot: string
let webPath: string
let backendPath: string

beforeAll(async () => {
  tmpRoot = await fsTempDir()
  webPath = path.join(tmpRoot, 'web')
  backendPath = path.join(tmpRoot, 'api')

  // ─── Frontend service: hits /api/screens/demandes/list + bogus orphan ───
  await mkdir(path.join(webPath, 'src/features/affaires/demande/services'), { recursive: true })
  await writeFile(
    path.join(webPath, 'src/features/affaires/demande/services/demandeService.ts'),
    `import { api } from '@atlashub/smartstack';
const API_PATH = '/api/screens/demandes';
const INTEGRATION_PATH = '/api/v1/integration/demandes';

export const demandeService = {
  getAll: async () => {
    const data = await api.get<unknown>(\`\${API_PATH}/list\`);
    return data;
  },
  getOrphanDashboard: async () => {
    // Pre-Wave-1 URL pattern that hits no backend route — the audit must flag this.
    const data = await api.get<unknown>('/api/affaires/accueil/dashboard');
    return data;
  },
  delete: async (id) => {
    await api.delete(\`\${INTEGRATION_PATH}/\${id}\`);
  },
};
`,
    'utf8',
  )

  // ─── Backend controller: screen + integration strata ────────────────────
  await mkdir(path.join(backendPath, 'src/Gaf.Api/Controllers/Affaires/Screens'), { recursive: true })
  await writeFile(
    path.join(backendPath, 'src/Gaf.Api/Controllers/Affaires/Screens/DemandesScreenController.cs'),
    `using Microsoft.AspNetCore.Mvc;
[ApiController]
[Route("api/screens/demandes")]
public class DemandesScreenController : ControllerBase
{
    [HttpGet("list")]
    public IActionResult List() => Ok();
}
`,
    'utf8',
  )
  await mkdir(path.join(backendPath, 'src/Gaf.Api/Controllers/Affaires'), { recursive: true })
  await writeFile(
    path.join(backendPath, 'src/Gaf.Api/Controllers/Affaires/DemandesController.cs'),
    `using Microsoft.AspNetCore.Mvc;
[ApiController]
[Route("api/v1/integration/demandes")]
public class DemandesController : ControllerBase
{
    [HttpDelete("{id:guid}")]
    public IActionResult Delete(System.Guid id) => NoContent();

    [HttpGet("lookup")]
    public IActionResult Lookup() => Ok();
}
`,
    'utf8',
  )
})

afterAll(async () => {
  if (tmpRoot) await rm(tmpRoot, { recursive: true, force: true })
})

describe('audit-dev-wire / DEV-WIRE-001', () => {
  it('flags the frontend orphan call as err (the user-visible 404)', async () => {
    const report = await audit({
      backendPath,
      webPath,
      moduleCode: 'affaires',
      appCode: 'gaf',
      mode: 'audit',
      rules: [],
    })

    const orphans = report.findings.filter(
      (f) => f.severity === 'err' && f.code === 'DEV-WIRE-001',
    )
    expect(orphans.length).toBeGreaterThanOrEqual(1)
    const dashboard = orphans.find((f) =>
      String(f.params.calls).includes('/api/affaires/accueil/dashboard'),
    )
    expect(dashboard).toBeDefined()
    // The audit should classify this URL as "unknown stratum" — it sits
    // outside both /api/v1/integration/* and /api/screens/*.
    expect(dashboard!.params.stratum).toBe('unknown')
    expect(dashboard!.fixSkill).toBeDefined()
  })

  it('does NOT flag matching list / delete / lookup pairs', async () => {
    const report = await audit({
      backendPath,
      webPath,
      moduleCode: 'affaires',
      appCode: 'gaf',
      mode: 'audit',
      rules: [],
    })

    const orphans = report.findings.filter(
      (f) => f.severity === 'err' && f.code === 'DEV-WIRE-001',
    )
    // The list call (GET /api/screens/demandes/list) has a matching backend
    // route. The delete call (DELETE /api/v1/integration/demandes/{id}) also.
    expect(orphans.find((f) => String(f.params.calls).includes('GET /api/screens/demandes/list'))).toBeUndefined()
    expect(orphans.find((f) => String(f.params.calls).includes('DELETE /api/v1/integration/demandes'))).toBeUndefined()
  })

  it('skips lookup endpoints from the backend-orphan warn list', async () => {
    const report = await audit({
      backendPath,
      webPath,
      moduleCode: 'affaires',
      appCode: 'gaf',
      mode: 'audit',
      rules: [],
    })

    // Backend has GET /api/v1/integration/demandes/lookup but no frontend
    // caller in this fixture. Lookup is on the well-known no-caller allow-list.
    const lookupWarn = report.findings.find(
      (f) =>
        f.severity === 'warn' &&
        String(f.params.route).includes('/api/v1/integration/demandes/lookup'),
    )
    expect(lookupWarn).toBeUndefined()
  })

  it('produces a non-zero exit signal (counts.errors > 0) when a frontend orphan exists', async () => {
    const report = await audit({
      backendPath,
      webPath,
      moduleCode: 'affaires',
      appCode: 'gaf',
      mode: 'audit',
      rules: [],
    })
    expect(report.counts.errors).toBeGreaterThan(0)
  })
})

async function fsTempDir(): Promise<string> {
  const dir = path.join(os.tmpdir(), `audit-dev-wire-test-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`)
  await mkdir(dir, { recursive: true })
  return dir
}
