import { test } from '@japa/runner'
import testUtils from '@adonisjs/core/services/test_utils'
import RelanceTask from '#models/relance_task'
import CheckinTask from '#models/checkin_task'
import Invoice from '#models/invoice'
import Organization from '#models/organization'
import { hashCheckinToken } from '#services/checkin_token'
import { createTestUser, createTwoOrgs } from '../helpers/auth.js'
import { body, type ApiOk, type ApiOkPaged } from '../helpers/response.js'
import { DateTime } from 'luxon'

type InvoiceShape = {
  id: string
  organizationId: string
  clientId: string
  numero: string
  amountTtcCents: number
  status: string
  rubisEarned: number
  paidAt: string | null
}

type ClientShape = { id: string; name: string }
type PlanShape = { id: string; slug: string }

async function createClient(client: any, headers: Record<string, string>, name: string) {
  const r = await client
    .post('/api/v1/clients')
    .headers(headers)
    .json({ name, email: `${name.toLowerCase().replace(/\s+/g, '')}@spec.test` })
  return body<ApiOk<ClientShape>>(r).data
}

async function getStandardPlan(client: any, headers: Record<string, string>) {
  const r = await client.get('/api/v1/plans/standard-30j').headers(headers)
  return body<ApiOk<PlanShape>>(r).data
}

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

  test('crée une facture (201) avec rubisEarned=1 (bonus saisie)', async ({ client, assert }) => {
    const { bearer } = await createTestUser()
    const c = await createClient(client, bearer, 'Boulangerie Spec')

    const r = await client.post('/api/v1/invoices').headers(bearer).json({
      clientId: c.id,
      clientName: c.name,
      numero: 'F-2026-T01',
      amountTtcCents: 124000,
      issueDate: '2026-04-01T09:00:00.000Z',
      dueDate: '2026-05-01T09:00:00.000Z',
    })

    r.assertStatus(201)
    const inv = body<ApiOk<InvoiceShape>>(r).data
    assert.equal(inv.rubisEarned, 1)
    assert.equal(inv.status, 'pending')
  })

  test('crée à la volée un client si nom non matché + email fourni', async ({ client, assert }) => {
    const { bearer, accessToken } = await createTestUser()

    const r = await client.post('/api/v1/invoices').headers(bearer).json({
      clientName: 'Nouveau Client',
      clientEmail: 'nouveau@spec.test',
      numero: 'F-2026-T02',
      amountTtcCents: 50000,
      issueDate: '2026-04-01T09:00:00.000Z',
      dueDate: '2026-05-01T09:00:00.000Z',
    })

    r.assertStatus(201)

    // Le client doit exister maintenant
    const list = await client.get('/api/v1/clients').headers(bearer)
    const names = body<ApiOk<Array<{ name: string }>>>(list).data.map((c) => c.name)
    assert.include(names, 'Nouveau Client')

    // assert le token est bien valide
    assert.isString(accessToken)
  })

  test('refuse création client à la volée sans email (422 client_email_required)', async ({
    client,
    assert,
  }) => {
    const { bearer } = await createTestUser()

    const r = await client.post('/api/v1/invoices').headers(bearer).json({
      clientName: 'Sans Email',
      numero: 'F-2026-T03',
      amountTtcCents: 50000,
      issueDate: '2026-04-01T09:00:00.000Z',
      dueDate: '2026-05-01T09:00:00.000Z',
    })

    r.assertStatus(422)
    const errors = body<{ errors: Array<{ code: string }> }>(r).errors
    assert.equal(errors[0].code, 'client_email_required')
  })

  test('schedule uniquement le check-in si planId est fourni', async ({ client, assert }) => {
    const { bearer } = await createTestUser()
    const c = await createClient(client, bearer, 'Avec Plan')
    const plan = await getStandardPlan(client, bearer)

    const r = await client.post('/api/v1/invoices').headers(bearer).json({
      clientId: c.id,
      clientName: c.name,
      planId: plan.id,
      numero: 'F-2026-T04',
      amountTtcCents: 50000,
      issueDate: '2026-04-01T09:00:00.000Z',
      dueDate: '2026-05-01T09:00:00.000Z',
    })

    r.assertStatus(201)
    const inv = body<ApiOk<InvoiceShape>>(r).data
    const tasks = await RelanceTask.query().where('invoice_id', inv.id)
    const checkins = await CheckinTask.query().where('invoice_id', inv.id)
    assert.lengthOf(tasks, 0)
    assert.lengthOf(checkins, 1)
    assert.equal(checkins[0].status, 'scheduled')
  })

  test('clic check-in pending programme les relances sans rafale passée', async ({
    client,
    assert,
  }) => {
    const { bearer, org } = await createTestUser()
    const c = await createClient(client, bearer, 'Checkin Pending')
    const plan = await getStandardPlan(client, bearer)
    const dueDate = DateTime.now().minus({ days: 20 }).set({ hour: 9, minute: 0, second: 0 })

    const created = await client
      .post('/api/v1/invoices')
      .headers(bearer)
      .json({
        clientId: c.id,
        clientName: c.name,
        planId: plan.id,
        numero: 'F-2026-CHECKIN',
        amountTtcCents: 50000,
        issueDate: dueDate.minus({ days: 15 }).toISO(),
        dueDate: dueDate.toISO(),
      })

    created.assertStatus(201)
    const inv = body<ApiOk<InvoiceShape>>(created).data
    const plain = 'pending-token-spec'
    const checkin = await CheckinTask.query().where('invoice_id', inv.id).firstOrFail()
    checkin.tokenHash = hashCheckinToken(plain)
    checkin.status = 'sent'
    checkin.sentAt = DateTime.now()
    await checkin.save()

    const beforeTasks = await RelanceTask.query().where('invoice_id', inv.id)
    assert.lengthOf(beforeTasks, 0)

    const pending = await client.get(`/api/v1/checkin/${plain}/pending`)
    pending.assertStatus(200)

    const invoice = await Invoice.findOrFail(inv.id)
    const tasks = await RelanceTask.query()
      .where('invoice_id', inv.id)
      .preload('planStep')
      .orderBy('send_at', 'asc')

    assert.equal(invoice.organizationId, org.id)
    assert.lengthOf(tasks, 3)
    for (const t of tasks) assert.equal(t.status, 'scheduled')
    assert.isAtMost(Math.abs(tasks[0].sendAt.diffNow('minutes').minutes), 2)
    assert.isAbove(tasks[1].sendAt.toMillis(), tasks[0].sendAt.toMillis())
  })

  test('numéro unique par org (422 duplicate)', async ({ client }) => {
    const { bearer } = await createTestUser()
    const c = await createClient(client, bearer, 'Dup Test')

    const payload = {
      clientId: c.id,
      clientName: c.name,
      numero: 'F-2026-DUP',
      amountTtcCents: 50000,
      issueDate: '2026-04-01T09:00:00.000Z',
      dueDate: '2026-05-01T09:00:00.000Z',
    }

    await client.post('/api/v1/invoices').headers(bearer).json(payload)
    const r2 = await client.post('/api/v1/invoices').headers(bearer).json(payload)
    r2.assertStatus(422)
  })
})

test.group('Invoices — POST /invoices/:id/mark-paid', (group) => {
  group.each.setup(() => testUtils.db().withGlobalTransaction())

  test('idempotent : 2e appel ne re-bumpe pas rubisEarned', async ({ client, assert }) => {
    const { bearer, org } = await createTestUser()
    const c = await createClient(client, bearer, 'Idem')
    const created = await client.post('/api/v1/invoices').headers(bearer).json({
      clientId: c.id,
      clientName: c.name,
      numero: 'F-2026-T10',
      amountTtcCents: 100000,
      issueDate: '2026-04-01T09:00:00.000Z',
      dueDate: '2026-05-01T09:00:00.000Z',
    })
    const id = body<ApiOk<InvoiceShape>>(created).data.id

    const first = await client.post(`/api/v1/invoices/${id}/mark-paid`).headers(bearer)
    first.assertStatus(200)
    const afterFirst = body<ApiOk<InvoiceShape>>(first).data
    assert.equal(afterFirst.status, 'paid')
    assert.equal(afterFirst.rubisEarned, 2) // 1 saisie + 1 encaissement

    const second = await client.post(`/api/v1/invoices/${id}/mark-paid`).headers(bearer)
    second.assertStatus(200)
    assert.equal(body<ApiOk<InvoiceShape>>(second).data.rubisEarned, 2) // pas re-bumpé

    // Org rubisCount n'a été incrémenté qu'une fois
    const orgFresh = await Organization.findOrFail(org.id)
    assert.equal(orgFresh.rubisCount, 1)
  })

  test('annule les RelanceTasks scheduled de la facture', async ({ client, assert }) => {
    const { bearer } = await createTestUser()
    const c = await createClient(client, bearer, 'Cancel Tasks')
    const plan = await getStandardPlan(client, bearer)

    const created = await client.post('/api/v1/invoices').headers(bearer).json({
      clientId: c.id,
      clientName: c.name,
      planId: plan.id,
      numero: 'F-2026-T11',
      amountTtcCents: 100000,
      issueDate: '2026-04-01T09:00:00.000Z',
      dueDate: '2026-05-01T09:00:00.000Z',
    })
    const id = body<ApiOk<InvoiceShape>>(created).data.id

    const plain = 'cancel-token-spec'
    const checkin = await CheckinTask.query().where('invoice_id', id).firstOrFail()
    checkin.tokenHash = hashCheckinToken(plain)
    checkin.status = 'sent'
    checkin.sentAt = DateTime.now()
    await checkin.save()

    const pending = await client.get(`/api/v1/checkin/${plain}/pending`)
    pending.assertStatus(200)

    const beforeTasks = await RelanceTask.query().where('invoice_id', id)
    assert.lengthOf(beforeTasks, 3)

    await client.post(`/api/v1/invoices/${id}/mark-paid`).headers(bearer)

    const afterTasks = await RelanceTask.query().where('invoice_id', id)
    for (const t of afterTasks) assert.equal(t.status, 'cancelled')
  })

  test('cross-org : user B ne peut pas mark-paid une facture de A (404)', async ({ client }) => {
    const { a, b } = await createTwoOrgs()
    const c = await createClient(client, a.bearer, 'Of A')
    const created = await client.post('/api/v1/invoices').headers(a.bearer).json({
      clientId: c.id,
      clientName: c.name,
      numero: 'F-2026-XO1',
      amountTtcCents: 50000,
      issueDate: '2026-04-01T09:00:00.000Z',
      dueDate: '2026-05-01T09:00:00.000Z',
    })
    const id = body<ApiOk<InvoiceShape>>(created).data.id

    const r = await client.post(`/api/v1/invoices/${id}/mark-paid`).headers(b.bearer)
    r.assertStatus(404)
  })
})

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

  test('liste paginée + meta total/page', async ({ client, assert }) => {
    const { bearer } = await createTestUser()
    const c = await createClient(client, bearer, 'Liste Spec')

    for (let i = 0; i < 3; i++) {
      await client
        .post('/api/v1/invoices')
        .headers(bearer)
        .json({
          clientId: c.id,
          clientName: c.name,
          numero: `F-2026-L${i}`,
          amountTtcCents: 10000 + i,
          issueDate: '2026-04-01T09:00:00.000Z',
          dueDate: '2026-05-01T09:00:00.000Z',
        })
    }

    const r = await client.get('/api/v1/invoices').headers(bearer)
    r.assertStatus(200)
    const payload = body<ApiOkPaged<InvoiceShape[]>>(r)
    assert.equal(payload.meta.total, 3)
    assert.equal(payload.meta.page, 1)
  })

  test('GET /invoices/counts agrège par status', async ({ client, assert }) => {
    const { bearer } = await createTestUser()
    const c = await createClient(client, bearer, 'Counts Spec')

    await client.post('/api/v1/invoices').headers(bearer).json({
      clientId: c.id,
      clientName: c.name,
      numero: 'F-2026-C01',
      amountTtcCents: 10000,
      issueDate: '2026-04-01T09:00:00.000Z',
      dueDate: '2026-05-01T09:00:00.000Z',
    })

    const r = await client.get('/api/v1/invoices/counts').headers(bearer)
    r.assertStatus(200)
    const c2 = body<ApiOk<{ all: number; pending: number }>>(r).data
    assert.equal(c2.all, 1)
    assert.equal(c2.pending, 1)
  })
})
