import { test } from '@japa/runner'
import testUtils from '@adonisjs/core/services/test_utils'
import { DateTime } from 'luxon'

import {
  PLAN_CAPS,
  canCreateInvoices,
  countActiveInvoices,
  getOrgSubscriptionState,
} from '#services/billing'
import Organization from '#models/organization'
import Client from '#models/client'
import Invoice from '#models/invoice'
import { createTestUser } from '../helpers/auth.js'

/**
 * Tests unitaires sur la logique de plans + enforcement.
 * On utilise une DB transaction par test (auto-rollback) pour isoler chaque
 * scénario sans pollution croisée. Pas de mock de DB — on teste la vraie
 * logique SQL `whereIn(status, ACTIVE_STATUSES)`.
 */

const ACTIVE_STATUSES = ['pending', 'awaiting_user_confirmation', 'in_relance', 'litigation'] as const
const INACTIVE_STATUSES = ['paid', 'cancelled'] as const

async function makeClientFor(org: Organization): Promise<Client> {
  return Client.create({
    organizationId: org.id,
    name: `Client ${Math.random().toString(36).slice(2, 8)}`,
    email: `client-${Math.random().toString(36).slice(2, 8)}@spec.test`,
    contactFirstName: null,
    contactLastName: null,
    phone: null,
    address: null,
    siret: null,
    notes: null,
  })
}

async function makeInvoice(
  org: Organization,
  client: Client,
  status: Invoice['status']
): Promise<Invoice> {
  const issue = DateTime.utc().minus({ days: 30 })
  return Invoice.create({
    organizationId: org.id,
    clientId: client.id,
    planId: null,
    numero: `F-${Math.random().toString(36).slice(2, 10)}`,
    amountTtcCents: 100_00,
    issueDate: issue,
    dueDate: issue.plus({ days: 30 }),
    paidAt: status === 'paid' ? DateTime.utc() : null,
    status,
    pdfStorageKey: null,
    rubisEarned: 0,
    notes: null,
  })
}

// ---------------------------------------------------------------------------
// PLAN_CAPS — sanity check sur les caps de chaque plan
// ---------------------------------------------------------------------------

test.group('billing — PLAN_CAPS', () => {
  test('Free : 5 factures max, 1 user, pas de multi-users', ({ assert }) => {
    assert.equal(PLAN_CAPS.free.activeInvoicesLimit, 5)
    assert.equal(PLAN_CAPS.free.seatsLimit, 1)
    assert.isFalse(PLAN_CAPS.free.multiUsers)
    assert.isFalse(PLAN_CAPS.free.replyFromUserEmail)
  })

  test('Pro : factures illimitées, 1 user', ({ assert }) => {
    assert.isNull(PLAN_CAPS.pro.activeInvoicesLimit)
    assert.equal(PLAN_CAPS.pro.seatsLimit, 1)
    assert.isFalse(PLAN_CAPS.pro.multiUsers)
  })

  test('Business : illimité + 5 sièges + reply-from-user', ({ assert }) => {
    assert.isNull(PLAN_CAPS.business.activeInvoicesLimit)
    assert.equal(PLAN_CAPS.business.seatsLimit, 5)
    assert.isTrue(PLAN_CAPS.business.multiUsers)
    assert.isTrue(PLAN_CAPS.business.replyFromUserEmail)
  })
})

// ---------------------------------------------------------------------------
// countActiveInvoices — compte les statuts pending/awaiting/in_relance/litigation
// ---------------------------------------------------------------------------

test.group('billing — countActiveInvoices', (group) => {
  group.each.setup(() => testUtils.db().withGlobalTransaction())

  test('compte les 4 statuts actifs', async ({ assert }) => {
    const { org } = await createTestUser()
    const client = await makeClientFor(org)
    for (const status of ACTIVE_STATUSES) {
      await makeInvoice(org, client, status)
    }
    const n = await countActiveInvoices(org.id)
    assert.equal(n, ACTIVE_STATUSES.length)
  })

  test('exclut les statuts inactifs (paid, cancelled)', async ({ assert }) => {
    const { org } = await createTestUser()
    const client = await makeClientFor(org)
    for (const status of INACTIVE_STATUSES) {
      await makeInvoice(org, client, status)
    }
    const n = await countActiveInvoices(org.id)
    assert.equal(n, 0)
  })

  test('isolation par org : ne compte pas les factures d\'une autre org', async ({ assert }) => {
    const { org: orgA } = await createTestUser()
    const { org: orgB } = await createTestUser()
    const clientB = await makeClientFor(orgB)
    await makeInvoice(orgB, clientB, 'pending')
    await makeInvoice(orgB, clientB, 'in_relance')
    const n = await countActiveInvoices(orgA.id)
    assert.equal(n, 0)
  })
})

// ---------------------------------------------------------------------------
// canCreateInvoices — règle d'enforcement principale
// ---------------------------------------------------------------------------

test.group('billing — canCreateInvoices', (group) => {
  group.each.setup(() => testUtils.db().withGlobalTransaction())

  test('Free + grace period active → autorisé sans limite', async ({ assert }) => {
    const { org } = await createTestUser()
    org.plan = 'free'
    org.gracePeriodEndsAt = DateTime.utc().plus({ months: 2 })
    await org.save()
    const client = await makeClientFor(org)
    // 50 factures actives, on devrait quand même pouvoir en ajouter
    for (let i = 0; i < 50; i++) await makeInvoice(org, client, 'pending')
    const result = await canCreateInvoices(org.id, 1)
    assert.isTrue(result.allowed)
  })

  test('Free post-grace + 4 actives → on peut en ajouter 1 (4+1 ≤ 5)', async ({ assert }) => {
    const { org } = await createTestUser()
    org.plan = 'free'
    org.gracePeriodEndsAt = DateTime.utc().minus({ days: 1 })
    await org.save()
    const client = await makeClientFor(org)
    for (let i = 0; i < 4; i++) await makeInvoice(org, client, 'pending')
    const result = await canCreateInvoices(org.id, 1)
    assert.isTrue(result.allowed)
  })

  test('Free post-grace + 5 actives → bloqué (5+1 > 5)', async ({ assert }) => {
    const { org } = await createTestUser()
    org.plan = 'free'
    org.gracePeriodEndsAt = DateTime.utc().minus({ days: 1 })
    await org.save()
    const client = await makeClientFor(org)
    for (let i = 0; i < 5; i++) await makeInvoice(org, client, 'in_relance')
    const result = await canCreateInvoices(org.id, 1)
    assert.isFalse(result.allowed)
    if (!result.allowed) {
      assert.equal(result.reason, 'free_limit_active_invoices')
      assert.equal(result.limit, 5)
      assert.equal(result.current, 5)
    }
  })

  test('Free post-grace + 3 actives + delta=3 → bloqué (3+3 > 5)', async ({ assert }) => {
    const { org } = await createTestUser()
    org.plan = 'free'
    org.gracePeriodEndsAt = DateTime.utc().minus({ days: 1 })
    await org.save()
    const client = await makeClientFor(org)
    for (let i = 0; i < 3; i++) await makeInvoice(org, client, 'pending')
    const result = await canCreateInvoices(org.id, 3)
    assert.isFalse(result.allowed)
  })

  test('Pro → toujours autorisé peu importe le compteur', async ({ assert }) => {
    const { org } = await createTestUser()
    org.plan = 'pro'
    org.gracePeriodEndsAt = DateTime.utc().minus({ months: 6 })
    await org.save()
    const client = await makeClientFor(org)
    for (let i = 0; i < 200; i++) await makeInvoice(org, client, 'in_relance')
    const result = await canCreateInvoices(org.id, 1)
    assert.isTrue(result.allowed)
  })

  test('Business → toujours autorisé', async ({ assert }) => {
    const { org } = await createTestUser()
    org.plan = 'business'
    await org.save()
    const client = await makeClientFor(org)
    for (let i = 0; i < 100; i++) await makeInvoice(org, client, 'in_relance')
    const result = await canCreateInvoices(org.id, 1)
    assert.isTrue(result.allowed)
  })

  test('Free post-grace : factures paid ne consomment pas de slot', async ({ assert }) => {
    const { org } = await createTestUser()
    org.plan = 'free'
    org.gracePeriodEndsAt = DateTime.utc().minus({ days: 1 })
    await org.save()
    const client = await makeClientFor(org)
    // 5 paid + 0 actives = encore 5 slots dispos
    for (let i = 0; i < 5; i++) await makeInvoice(org, client, 'paid')
    const result = await canCreateInvoices(org.id, 5)
    assert.isTrue(result.allowed)
  })
})

// ---------------------------------------------------------------------------
// getOrgSubscriptionState — shape & cohérence
// ---------------------------------------------------------------------------

test.group('billing — getOrgSubscriptionState', (group) => {
  group.each.setup(() => testUtils.db().withGlobalTransaction())

  test('Free + grace active → inGracePeriod=true', async ({ assert }) => {
    const { org } = await createTestUser()
    org.plan = 'free'
    org.gracePeriodEndsAt = DateTime.utc().plus({ months: 2 })
    await org.save()
    const state = await getOrgSubscriptionState(org.id)
    assert.equal(state.plan, 'free')
    assert.isTrue(state.inGracePeriod)
    assert.equal(state.activeInvoicesCount, 0)
    assert.equal(state.caps.activeInvoicesLimit, 5)
    assert.isFalse(state.hasStripeCustomer)
  })

  test('Free post-grace → inGracePeriod=false', async ({ assert }) => {
    const { org } = await createTestUser()
    org.plan = 'free'
    org.gracePeriodEndsAt = DateTime.utc().minus({ days: 1 })
    await org.save()
    const state = await getOrgSubscriptionState(org.id)
    assert.isFalse(state.inGracePeriod)
  })

  test('Pro avec subscription → reflète status et period_end Stripe', async ({ assert }) => {
    const { org } = await createTestUser()
    org.plan = 'pro'
    org.subscriptionStatus = 'active'
    org.billingCycle = 'monthly'
    org.currentPeriodEnd = DateTime.utc().plus({ days: 23 })
    org.stripeCustomerId = 'cus_test_123'
    await org.save()
    const state = await getOrgSubscriptionState(org.id)
    assert.equal(state.plan, 'pro')
    assert.equal(state.subscriptionStatus, 'active')
    assert.equal(state.billingCycle, 'monthly')
    assert.isNotNull(state.currentPeriodEnd)
    assert.isTrue(state.hasStripeCustomer)
    assert.isFalse(state.inGracePeriod) // les payants ne sont jamais "en grâce"
  })

  test('Compteur actif inclut bien les 4 statuts', async ({ assert }) => {
    const { org } = await createTestUser()
    const client = await makeClientFor(org)
    for (const status of ACTIVE_STATUSES) {
      await makeInvoice(org, client, status)
    }
    await makeInvoice(org, client, 'paid')
    await makeInvoice(org, client, 'cancelled')
    const state = await getOrgSubscriptionState(org.id)
    assert.equal(state.activeInvoicesCount, ACTIVE_STATUSES.length)
  })
})
