import { test } from '@japa/runner'
import testUtils from '@adonisjs/core/services/test_utils'
import Client from '#models/client'
import { createTestUser, createTwoOrgs } from '../helpers/auth.js'
import { body, type ApiError, type ApiOk, type ApiConflict } from '../helpers/response.js'

type ClientShape = {
  id: string
  organizationId: string
  name: string
  email: string
  phone: string | null
  address: string | null
  siret: string | null
  notes: string | null
}

test.group('Clients — POST /clients', (group) => {
  group.each.setup(() => testUtils.db().withGlobalTransaction())

  test('refuse sans email (422 + field=email)', async ({ client, assert }) => {
    const { bearer } = await createTestUser()
    const response = await client
      .post('/api/v1/clients')
      .headers(bearer)
      .json({ name: 'Boulangerie X' })

    response.assertStatus(422)
    assert.equal(body<ApiError>(response).errors[0].field, 'email')
  })

  test('refuse SIRET non-14-chiffres (422)', async ({ client }) => {
    const { bearer } = await createTestUser()
    const response = await client
      .post('/api/v1/clients')
      .headers(bearer)
      .json({
        name: 'Test SIRET',
        email: 'compta@test.fr',
        siret: '123', // 3 chiffres au lieu de 14
      })
    response.assertStatus(422)
  })

  test("crée un client (201) + UUID + association à l'org du user", async ({
    client,
    assert,
  }) => {
    const { bearer, org } = await createTestUser()
    const response = await client
      .post('/api/v1/clients')
      .headers(bearer)
      .json({ name: 'Boulangerie X', email: 'compta@x.fr' })

    response.assertStatus(201)
    const data = body<ApiOk<ClientShape>>(response).data
    assert.match(data.id, /^[0-9a-f-]{36}$/u)
    assert.equal(data.organizationId, org.id)
    assert.equal(data.email, 'compta@x.fr')
  })

  test('rejette doublon par nom case-insensitive (409 + payload existing)', async ({
    client,
    assert,
  }) => {
    const { bearer } = await createTestUser()

    await client
      .post('/api/v1/clients')
      .headers(bearer)
      .json({ name: 'Boulangerie Martin', email: 'a@martin.fr' })

    const dup = await client
      .post('/api/v1/clients')
      .headers(bearer)
      .json({ name: 'BOULANGERIE MARTIN', email: 'b@martin.fr' })

    dup.assertStatus(409)
    const payload = body<ApiConflict<ClientShape>>(dup)
    assert.equal(payload.errors[0].code, 'duplicate_client')
    assert.exists(payload.existing.id)
  })
})

test.group('Clients — GET /clients', (group) => {
  group.each.setup(() => testUtils.db().withGlobalTransaction())

  test("liste seulement les clients de l'org du user (cross-org)", async ({
    client,
    assert,
  }) => {
    const { a, b } = await createTwoOrgs()

    await client
      .post('/api/v1/clients')
      .headers(a.bearer)
      .json({ name: 'Client A1', email: 'a1@a.fr' })

    await client
      .post('/api/v1/clients')
      .headers(b.bearer)
      .json({ name: 'Client B1', email: 'b1@b.fr' })

    const fromA = await client.get('/api/v1/clients').headers(a.bearer)
    fromA.assertStatus(200)
    const namesFromA = body<ApiOk<ClientShape[]>>(fromA).data.map((c) => c.name)
    assert.deepEqual(namesFromA, ['Client A1'])

    const fromB = await client.get('/api/v1/clients').headers(b.bearer)
    fromB.assertStatus(200)
    const namesFromB = body<ApiOk<ClientShape[]>>(fromB).data.map((c) => c.name)
    assert.deepEqual(namesFromB, ['Client B1'])
  })

  test('?withStats=1 enrichit avec compteurs (zéros sans factures)', async ({
    client,
    assert,
  }) => {
    const { bearer } = await createTestUser()
    await client
      .post('/api/v1/clients')
      .headers(bearer)
      .json({ name: 'Boulangerie', email: 'a@b.fr' })

    const response = await client.get('/api/v1/clients?withStats=1').headers(bearer)
    response.assertStatus(200)
    const c = body<ApiOk<Array<ClientShape & {
      invoiceCount: number
      lateInvoiceCount: number
      paidLifetimeCents: number
      lastActivityAt: string | null
    }>>>(response).data[0]
    assert.equal(c.invoiceCount, 0)
    assert.equal(c.lateInvoiceCount, 0)
    assert.equal(c.paidLifetimeCents, 0)
    assert.isNull(c.lastActivityAt)
  })

  test('?q=foo filtre par nom et email (ILIKE)', async ({ client, assert }) => {
    const { bearer } = await createTestUser()
    await client
      .post('/api/v1/clients')
      .headers(bearer)
      .json({ name: 'Boulangerie Martin', email: 'martin@bp.fr' })
    await client
      .post('/api/v1/clients')
      .headers(bearer)
      .json({ name: 'Atelier Durand', email: 'durand@a.fr' })

    const r = await client.get('/api/v1/clients?q=BOULANG').headers(bearer)
    r.assertStatus(200)
    const data = body<ApiOk<ClientShape[]>>(r).data
    assert.lengthOf(data, 1)
    assert.equal(data[0].name, 'Boulangerie Martin')
  })
})

test.group('Clients — perms cross-org', (group) => {
  group.each.setup(() => testUtils.db().withGlobalTransaction())

  test("user B ne peut pas GET /clients/:id d'un client de A (404)", async ({
    client,
  }) => {
    const { a, b } = await createTwoOrgs()
    const created = await client
      .post('/api/v1/clients')
      .headers(a.bearer)
      .json({ name: 'Client A', email: 'a@a.fr' })
    const id = body<ApiOk<ClientShape>>(created).data.id

    const fromB = await client.get(`/api/v1/clients/${id}`).headers(b.bearer)
    fromB.assertStatus(404)
  })

  test("user B ne peut pas PATCH /clients/:id d'un client de A (404)", async ({
    client,
    assert,
  }) => {
    const { a, b } = await createTwoOrgs()
    const created = await client
      .post('/api/v1/clients')
      .headers(a.bearer)
      .json({ name: 'Client A', email: 'a@a.fr' })
    const id = body<ApiOk<ClientShape>>(created).data.id

    const r = await client
      .patch(`/api/v1/clients/${id}`)
      .headers(b.bearer)
      .json({ phone: '06 11 22 33 44' })
    r.assertStatus(404)

    // Le client A n'a pas été touché
    const fresh = await Client.findOrFail(id)
    assert.isNull(fresh.phone)
  })
})
