import { BaseCommand, flags } from '@adonisjs/core/ace'
import type { CommandOptions } from '@adonisjs/core/types/ace'
import { DateTime } from 'luxon'

import User from '#models/user'
import Organization from '#models/organization'
import Client from '#models/client'
import Invoice from '#models/invoice'
import { canCreateInvoices, countActiveInvoices, PLAN_CAPS } from '#services/billing'

/**
 * Force un état billing sur l'org d'un user pour tester rapidement chaque
 * comportement de l'UI/enforcement sans passer par Stripe ni attendre 3 mois.
 *
 * Usage :
 *
 *   node ace billing:scenario --email arthurbarre.js@gmail.com --scenario status
 *   node ace billing:scenario --email arthurbarre.js@gmail.com --scenario fresh
 *   node ace billing:scenario --email arthurbarre.js@gmail.com --scenario grace-expired
 *   node ace billing:scenario --email arthurbarre.js@gmail.com --scenario limit-reached
 *   node ace billing:scenario --email arthurbarre.js@gmail.com --scenario pro
 *   node ace billing:scenario --email arthurbarre.js@gmail.com --scenario pro-cancelling
 *   node ace billing:scenario --email arthurbarre.js@gmail.com --scenario pro-past-due
 *   node ace billing:scenario --email arthurbarre.js@gmail.com --scenario business
 *
 * Scénarios :
 *  - status         : ne touche rien, affiche juste l'état courant
 *  - fresh          : reset à un signup neuf (free + grace 3 mois)
 *  - grace-expired  : free, grace terminée, ≤ 5 factures actives → OK
 *  - limit-reached  : free, grace terminée, 5 factures actives forcées → bloqué
 *  - pro            : pro mensuel actif, fake Stripe IDs
 *  - pro-cancelling : pro mais annulation programmée à period_end
 *  - pro-past-due   : pro mais paiement échoué (status past_due)
 *  - business       : business mensuel actif
 */

const SCENARIOS = [
  'status',
  'fresh',
  'grace-expired',
  'limit-reached',
  'pro',
  'pro-cancelling',
  'pro-past-due',
  'business',
] as const
type Scenario = (typeof SCENARIOS)[number]

export default class BillingScenario extends BaseCommand {
  static commandName = 'billing:scenario'
  static description =
    "Force un état billing sur un user pour tester les comportements UI/enforcement"

  static options: CommandOptions = {
    startApp: true,
  }

  @flags.string({
    description: "Email du user à modifier",
    required: true,
  })
  declare email: string

  @flags.string({
    description: `Scénario : ${SCENARIOS.join(' | ')}`,
    required: true,
  })
  declare scenario: string

  async run() {
    const email = String(this.email).toLowerCase()
    const user = await User.findBy('email', email)
    if (!user || !user.organizationId) {
      this.logger.error(`User ${email} introuvable ou sans organisation`)
      this.exitCode = 1
      return
    }
    const org = await Organization.findOrFail(user.organizationId)

    if (!SCENARIOS.includes(this.scenario as Scenario)) {
      this.logger.error(
        `Scénario inconnu "${this.scenario}". Valides : ${SCENARIOS.join(', ')}`
      )
      this.exitCode = 1
      return
    }

    switch (this.scenario as Scenario) {
      case 'status':
        break
      case 'fresh':
        await this.applyFresh(org)
        break
      case 'grace-expired':
        await this.applyGraceExpired(org)
        break
      case 'limit-reached':
        await this.applyLimitReached(org)
        break
      case 'pro':
        await this.applyPro(org)
        break
      case 'pro-cancelling':
        await this.applyProCancelling(org)
        break
      case 'pro-past-due':
        await this.applyProPastDue(org)
        break
      case 'business':
        await this.applyBusiness(org)
        break
    }

    await org.refresh()
    await this.printStatus(org)
  }

  // ---------------------------------------------------------------------------
  // Scénarios
  // ---------------------------------------------------------------------------

  private async applyFresh(org: Organization) {
    org.plan = 'free'
    org.gracePeriodEndsAt = DateTime.utc().plus({ months: 3 })
    org.stripeCustomerId = null
    org.stripeSubscriptionId = null
    org.subscriptionStatus = null
    org.billingCycle = null
    org.currentPeriodEnd = null
    org.cancelAtPeriodEnd = false
    await org.save()
    this.logger.info('→ Free, grace period 3 mois fresh, aucun Stripe customer')
  }

  private async applyGraceExpired(org: Organization) {
    org.plan = 'free'
    org.gracePeriodEndsAt = DateTime.utc().minus({ days: 1 })
    org.stripeCustomerId = null
    org.stripeSubscriptionId = null
    org.subscriptionStatus = null
    org.billingCycle = null
    org.currentPeriodEnd = null
    org.cancelAtPeriodEnd = false
    await org.save()
    this.logger.info(
      '→ Free, grace period expirée. Si actives ≤ 5 : OK. Si > 5 : bloqué.'
    )
  }

  private async applyLimitReached(org: Organization) {
    await this.applyGraceExpired(org)
    // S'assurer qu'il y a au moins 5 factures actives. Si moins, on en crée
    // de quoi passer à 5 minimum.
    const current = await countActiveInvoices(org.id)
    const limit = PLAN_CAPS.free.activeInvoicesLimit ?? 5
    if (current >= limit) {
      this.logger.info(`→ Déjà ${current} factures actives (≥ ${limit}), OK`)
      return
    }
    const need = limit - current
    this.logger.info(`→ Création de ${need} factures dummy pour atteindre la limite`)
    await this.createDummyInvoices(org, need)
  }

  private async applyPro(org: Organization) {
    org.plan = 'pro'
    org.gracePeriodEndsAt = null
    // Préserve les VRAIS Stripe IDs si l'org en a déjà (= a déjà payé).
    // Sinon, fake IDs pour que l'UI affiche le bouton "Manage" etc.
    org.stripeCustomerId =
      org.stripeCustomerId ?? `cus_test_FAKE_${org.id.slice(0, 8)}`
    org.stripeSubscriptionId =
      org.stripeSubscriptionId ?? `sub_test_FAKE_${org.id.slice(0, 8)}`
    org.subscriptionStatus = 'active'
    org.billingCycle = 'monthly'
    org.currentPeriodEnd = DateTime.utc().plus({ days: 28 })
    org.cancelAtPeriodEnd = false
    await org.save()
    this.logger.info('→ Pro mensuel actif, period_end dans 28 jours')
  }

  private async applyProCancelling(org: Organization) {
    await this.applyPro(org)
    org.cancelAtPeriodEnd = true
    await org.save()
    this.logger.info('→ Pro mais annulation programmée (cancel_at_period_end=true)')
  }

  private async applyProPastDue(org: Organization) {
    await this.applyPro(org)
    org.subscriptionStatus = 'past_due'
    await org.save()
    this.logger.info('→ Pro mais paiement échoué (status=past_due)')
  }

  private async applyBusiness(org: Organization) {
    org.plan = 'business'
    org.gracePeriodEndsAt = null
    // Préserve les vrais Stripe IDs s'ils existent (cf. applyPro).
    org.stripeCustomerId =
      org.stripeCustomerId ?? `cus_test_FAKE_${org.id.slice(0, 8)}`
    org.stripeSubscriptionId =
      org.stripeSubscriptionId ?? `sub_test_FAKE_${org.id.slice(0, 8)}`
    org.subscriptionStatus = 'active'
    org.billingCycle = 'monthly'
    org.currentPeriodEnd = DateTime.utc().plus({ days: 28 })
    org.cancelAtPeriodEnd = false
    await org.save()
    this.logger.info('→ Business mensuel actif')
  }

  // ---------------------------------------------------------------------------
  // Helpers
  // ---------------------------------------------------------------------------

  /**
   * Crée des factures dummy en `pending` (= active) pour gonfler artificiellement
   * le compteur. Si l'org n'a pas de client, on en crée un aussi.
   */
  private async createDummyInvoices(org: Organization, count: number) {
    let client = await Client.query()
      .where('organization_id', org.id)
      .first()
    if (!client) {
      client = await Client.create({
        organizationId: org.id,
        name: 'Client Test Billing',
        email: 'billing-test@example.com',
        contactFirstName: null,
        contactLastName: null,
        phone: null,
        address: null,
        siret: null,
        notes: null,
      })
    }

    const now = DateTime.utc()
    for (let i = 0; i < count; i++) {
      await Invoice.create({
        organizationId: org.id,
        clientId: client.id,
        planId: null,
        numero: `BILLING-TEST-${Date.now()}-${i}`,
        amountTtcCents: 50_000,
        issueDate: now.minus({ days: 5 }),
        dueDate: now.plus({ days: 25 }),
        paidAt: null,
        status: 'pending',
        pdfStorageKey: null,
        rubisEarned: 0,
        notes: null,
      })
    }
  }

  /** Affiche l'état courant d'une façon lisible. */
  private async printStatus(org: Organization) {
    const activeCount = await countActiveInvoices(org.id)
    const enforcement = await canCreateInvoices(org.id, 1)
    const caps = PLAN_CAPS[(org.plan ?? 'free') as keyof typeof PLAN_CAPS]
    const inGrace = !!(org.gracePeriodEndsAt && org.gracePeriodEndsAt > DateTime.utc())

    this.logger.info('')
    this.logger.info('────────── ÉTAT BILLING ──────────')
    this.logger.info(`Org              : ${org.id}  "${org.name}"`)
    this.logger.info(`Plan             : ${org.plan}`)
    this.logger.info(
      `Grace period     : ${
        org.gracePeriodEndsAt
          ? `${org.gracePeriodEndsAt.toFormat('yyyy-LL-dd')} (${inGrace ? 'EN COURS' : 'TERMINÉE'})`
          : 'NONE'
      }`
    )
    this.logger.info(`Stripe customer  : ${org.stripeCustomerId ?? '—'}`)
    this.logger.info(`Subscription     : ${org.stripeSubscriptionId ?? '—'}`)
    this.logger.info(
      `Status           : ${org.subscriptionStatus ?? '—'} (${org.billingCycle ?? 'no cycle'})`
    )
    this.logger.info(
      `Period end       : ${org.currentPeriodEnd?.toFormat('yyyy-LL-dd') ?? '—'}`
    )
    this.logger.info(`Cancel scheduled : ${org.cancelAtPeriodEnd ? '✓ OUI' : '✗ non'}`)
    this.logger.info('')
    this.logger.info(
      `Factures actives : ${activeCount} / ${
        caps.activeInvoicesLimit ?? '∞'
      }`
    )
    this.logger.info(
      `Création autorisée (delta=1) : ${
        enforcement.allowed ? '✓ YES' : '✗ BLOQUÉ'
      }`
    )
    if (!enforcement.allowed) {
      this.logger.info(
        `   Raison : ${enforcement.reason}, limite=${enforcement.limit}, courant=${enforcement.current}`
      )
    }
    this.logger.info('───────────────────────────────────')
    this.logger.info('')
    this.logger.info('Reload /parametres/abonnement et /factures côté SPA pour voir.')
  }
}

