/**
 * url-parity.test.ts — Locks the frontend api-client URL ↔ backend
 * scaffolders URL agreement at every legal input. Added 2026-05-27 (Wave 1
 * of the URL-convention alignment) to close the
 *   GET /api/affaires/accueil/dashboard 404
 *   GET /api/gaf/referentiels/rues 404
 * class of bugs documented in `plans/audit-le-code-qui-streamed-taco.md`.
 *
 * The contract:
 *   useScreens=false → both sides hit /api/v1/integration/{plural-kebab}
 *   useScreens=true  → both sides hit /api/screens/{plural-kebab}
 * No third URL pattern is ever legal — `apiBasePath` was removed from the
 * scaffold-api-client schema.
 */
import { describe, it, expect } from 'vitest'
import { generate as generateApiClient } from '../generate.js'
import { ScaffoldApiClientInputSchema, type ScaffoldApiClientInput } from '../types.js'
import { generate as generateController } from '../../../../../backend/controller/cli/scaffold-controller/generate.js'
import type { ScaffoldControllerInput } from '../../../../../backend/controller/cli/scaffold-controller/types.js'
import { generate as generateScreenController } from '../../../../../backend/screen-controller/cli/scaffold-screen-controller/generate.js'
import type { PageSpec, ScaffoldScreenControllerSpec } from '../../../../../backend/screen-controller/cli/scaffold-screen-controller/types.js'

function apiClientFixture(overrides: Partial<ScaffoldApiClientInput> = {}): ScaffoldApiClientInput {
  return {
    module: 'affaires',
    appCode: 'gaf',
    entities: [
      {
        name: 'Demande',
        pluralName: 'Demandes',
        section: 'demandes',
        hasDashboard: false,
        fields: [{ name: 'code', type: 'string', required: true }],
      },
    ],
    projectPath: '/web',
    ...overrides,
  } as ScaffoldApiClientInput
}

function controllerFixture(overrides: Partial<ScaffoldControllerInput> = {}): ScaffoldControllerInput {
  return {
    name: 'Demande',
    pluralName: 'Demandes',
    module: 'affaires',
    section: 'demandes',
    appCode: 'gaf',
    namespace: 'Gaf',
    navRoute: 'affaires.demandes',
    permissionPrefix: 'affaires.demandes',
    actions: ['read', 'create', 'update', 'delete'],
    customActions: [],
    fields: [{ name: 'code', type: 'string', required: true }],
    projectPath: '/api',
    ...overrides,
  } as ScaffoldControllerInput
}

function screenSpec(overrides: Partial<ScaffoldScreenControllerSpec> = {}): ScaffoldScreenControllerSpec {
  return {
    section: 'demandes',
    entity: 'Demande',
    module: 'affaires',
    appCode: 'gaf',
    namespace: 'Gaf',
    moduleDir: '/ba/affaires',
    projectPath: '/api',
    ...overrides,
  } as ScaffoldScreenControllerSpec
}

function listPagespec(overrides: Partial<PageSpec> = {}): PageSpec {
  return {
    screenCode: 'SCR-AFFAIRES-DEMANDES-LIST',
    appCode: 'gaf',
    module: 'affaires',
    section: 'demandes',
    entity: 'Demande',
    view: 'list',
    permission: 'affaires.demandes.read',
    linkedUseCases: [],
    linkedBusinessRules: [],
    columns: [],
    actions: [],
    ...overrides,
  } as PageSpec
}

describe('URL parity — frontend api-client ↔ backend controllers', () => {
  it('routeMode=integration → both sides on /api/v1/integration/{plural}', () => {
    const fe = generateApiClient(apiClientFixture({ routeMode: 'integration' }))
    const feService = fe.find((f) => f.path.endsWith('demandeService.ts'))!
    expect(feService.content).toMatch(
      /const API_PATH = '\/api\/v1\/integration\/demandes';/,
    )

    const be = generateController(controllerFixture())
    const beCtrl = be.find((f) => f.path.endsWith('DemandesController.cs'))!
    expect(beCtrl.content).toMatch(
      /\[Route\("api\/v1\/integration\/demandes"\)\]/,
    )
  })

  it('useScreens=true → both sides on /api/screens/{plural}', () => {
    const fe = generateApiClient(apiClientFixture({ useScreens: true }))
    const feService = fe.find((f) => f.path.endsWith('demandeService.ts'))!
    expect(feService.content).toMatch(/const API_PATH = '\/api\/screens\/demandes';/)
    // Lookup + delete still on the integration stratum (the screen-driven
    // contract intentionally omits them).
    expect(feService.content).toMatch(
      /const INTEGRATION_PATH = '\/api\/v1\/integration\/demandes';/,
    )

    const be = generateScreenController(screenSpec(), [listPagespec()])
    const beCtrl = be.files.find((f) => f.path.endsWith('DemandesScreenController.cs'))!
    expect(beCtrl.content).toMatch(/\[Route\("api\/screens\/demandes"\)\]/)
  })

  it('multi-word entities use the SAME kebab segment on both sides', () => {
    const fe = generateApiClient(
      apiClientFixture({
        routeMode: 'integration',
        entities: [{
          name: 'OrderLine',
          pluralName: 'OrderLines',
          section: 'orders',
          hasDashboard: false,
          fields: [{ name: 'qty', type: 'int', required: true }],
        }],
      }),
    )
    const feService = fe.find((f) => f.path.endsWith('orderLineService.ts'))!
    expect(feService.content).toMatch(
      /const API_PATH = '\/api\/v1\/integration\/order-lines';/,
    )

    const be = generateController(
      controllerFixture({ name: 'OrderLine', pluralName: 'OrderLines' }),
    )
    const beCtrl = be.find((f) => f.path.endsWith('OrderLinesController.cs'))!
    expect(beCtrl.content).toMatch(/\[Route\("api\/v1\/integration\/order-lines"\)\]/)

    const bes = generateScreenController(
      screenSpec({ entity: 'OrderLine' }),
      [listPagespec({ entity: 'OrderLine' })],
    )
    const besCtrl = bes.files.find((f) => f.path.endsWith('OrderLinesScreenController.cs'))!
    expect(besCtrl.content).toMatch(/\[Route\("api\/screens\/order-lines"\)\]/)
  })

  it('dashboard URL on the frontend points where scaffold-screen-controller emits it', () => {
    // The original 404 — /api/affaires/accueil/dashboard — happened because
    // the api-client's `apiBasePath` template injected `{section}` into the
    // path while the screen controller emits /api/screens/{plural}/dashboard/*.
    // With apiBasePath gone, the dashboard URL becomes the canonical one.
    const fe = generateApiClient(
      apiClientFixture({
        useScreens: true,
        entities: [{
          name: 'Demande',
          pluralName: 'Demandes',
          section: 'accueil',
          hasDashboard: true,
          fields: [],
        }],
      }),
    )
    const feService = fe.find((f) => f.path.endsWith('demandeService.ts'))!
    // No section name in the URL — section is metadata, not a path segment.
    expect(feService.content).not.toMatch(/\/api\/affaires\/accueil/)
    expect(feService.content).toMatch(/\$\{API_PATH\}\/dashboard`/)
    expect(feService.content).not.toMatch(/dashboard\/consolidated/)

    const be = generateScreenController(
      screenSpec(),
      [listPagespec({ view: 'dashboard', screenCode: 'SCR-AFFAIRES-DEMANDES-DASH' })],
    )
    const beCtrl = be.files.find((f) => f.path.endsWith('DemandesScreenController.cs'))!
    expect(beCtrl.content).toMatch(/\[HttpGet\("dashboard"\)\]/)
  })
})

describe('apiBasePath removal — Zod schema rejects orphaned URL templates', () => {
  it('rejects a spec carrying the deprecated apiBasePath field', () => {
    const result = ScaffoldApiClientInputSchema.safeParse({
      ...apiClientFixture(),
      // The historical free-form template that produced /api/{appCode}/{module}/{section}/...
      // is no longer accepted: the schema is .strict() so unknown keys fail.
      apiBasePath: '/api/{appCode}/{module}/{section}',
    })
    expect(result.success).toBe(false)
    if (!result.success) {
      const message = JSON.stringify(result.error.issues)
      expect(message.toLowerCase()).toMatch(/apibasepath|unrecognized/)
    }
  })

  it('accepts a spec WITHOUT apiBasePath (the new default)', () => {
    const result = ScaffoldApiClientInputSchema.safeParse(apiClientFixture())
    expect(result.success).toBe(true)
  })
})
