import Plan from '#models/plan'
import PlanStep from '#models/plan_step'
import PlanTransformer from '#transformers/plan_transformer'
import { createPlanValidator, updatePlanValidator } from '#validators/plan'
import type { HttpContext } from '@adonisjs/core/http'
import { Exception } from '@adonisjs/core/exceptions'
import db from '@adonisjs/lucid/services/db'

/**
 * Slug à partir d'un nom de plan : minuscules, ASCII safe, tirets.
 * On garantit l'unicité par org en suffixant un compteur si collision.
 */
function slugify(input: string): string {
  return input
    .normalize('NFD')
    .replace(/[̀-ͯ]/g, '')
    .toLowerCase()
    .replace(/[^a-z0-9]+/g, '-')
    .replace(/^-+|-+$/g, '')
    .slice(0, 60) || 'plan'
}

// Slugs réservés côté front (routes statiques type /plans/nouveau).
// Si l'utilisateur nomme son plan "nouveau", on suffixe d'office.
const RESERVED_SLUGS = new Set(['nouveau', 'new', 'create'])

async function nextAvailableSlug(organizationId: string, base: string): Promise<string> {
  const start = RESERVED_SLUGS.has(base) ? `${base}-1` : base
  const existing = await Plan.query()
    .where('organization_id', organizationId)
    .whereILike('slug', `${base}%`)
    .select('slug')
  const taken = new Set(existing.map((p) => p.slug))
  if (!taken.has(start) && !RESERVED_SLUGS.has(start)) return start
  for (let i = 2; i < 100; i++) {
    const candidate = `${base}-${i}`
    if (!taken.has(candidate) && !RESERVED_SLUGS.has(candidate)) return candidate
  }
  return `${base}-${Date.now()}`
}

const ACTIVE_INVOICE_STATUSES = "('pending','in_relance','awaiting_user_confirmation')"

function requireOrgId(auth: HttpContext['auth']): string {
  const user = auth.getUserOrFail()
  if (!user.organizationId) {
    throw new Exception('Aucune organisation rattachée', { status: 404, code: 'not_found' })
  }
  return user.organizationId
}

function serializePlan(p: Plan) {
  return new PlanTransformer(p).toObject()
}

/**
 * Compte combien de factures actives (non payées, non annulées) référencent
 * chaque plan d'une org. Utilisé pour enrichir la liste avec un badge "X
 * factures utilisent ce plan" — utile avant édition pour signaler l'impact.
 */
async function bulkComputePlanUsage(
  organizationId: string,
  planIds: string[]
): Promise<Map<string, number>> {
  const map = new Map<string, number>()
  for (const id of planIds) map.set(id, 0)

  if (planIds.length === 0) return map

  const rows = await db
    .from('invoices')
    .where('organization_id', organizationId)
    .whereIn('plan_id', planIds)
    .whereRaw(`status::text in ${ACTIVE_INVOICE_STATUSES}`)
    .select('plan_id')
    .count('* as count')
    .groupBy('plan_id')

  for (const r of rows) {
    map.set(r.plan_id, Number(r.count))
  }
  return map
}

export default class PlansController {
  /**
   * GET /plans — liste enrichie avec compteurs d'usage.
   */
  async index({ auth, response }: HttpContext) {
    const organizationId = requireOrgId(auth)

    const plans = await Plan.query()
      .where('organization_id', organizationId)
      .preload('steps')
      .orderBy('is_default', 'desc')
      .orderBy('name', 'asc')

    const usage = await bulkComputePlanUsage(
      organizationId,
      plans.map((p) => p.id)
    )

    const data = plans.map((p) => ({
      ...serializePlan(p),
      usageCount: usage.get(p.id) ?? 0,
    }))

    return response.json({ data })
  }

  /**
   * GET /plans/:slug — détail.
   * Le SPA lookup par slug pour les plans pré-fournis (URL stable et
   * lisible : /parametres/plans/standard-30j).
   */
  async show({ auth, params, response }: HttpContext) {
    const organizationId = requireOrgId(auth)

    const plan = await Plan.query()
      .where('organization_id', organizationId)
      .where('slug', params.slug)
      .preload('steps')
      .first()

    if (!plan) {
      throw new Exception('Plan introuvable', { status: 404, code: 'not_found' })
    }

    const usage = await bulkComputePlanUsage(organizationId, [plan.id])
    return response.json({
      data: { ...serializePlan(plan), usageCount: usage.get(plan.id) ?? 0 },
    })
  }

  /**
   * PATCH /plans/:slug — édite nom, description et/ou recompose les étapes.
   *
   * Recomposition des steps : on ne fait pas de diff fin (id par id), on
   * remplace tout le set en transaction. Plus simple, plus prévisible, et
   * idiomatique côté UX (l'utilisateur a édité son plan dans son ensemble).
   */
  async update({ auth, params, request, response }: HttpContext) {
    const organizationId = requireOrgId(auth)
    const payload = await request.validateUsing(updatePlanValidator)

    const plan = await Plan.query()
      .where('organization_id', organizationId)
      .where('slug', params.slug)
      .first()

    if (!plan) {
      throw new Exception('Plan introuvable', { status: 404, code: 'not_found' })
    }

    await db.transaction(async (trx) => {
      plan.useTransaction(trx)
      if (payload.name !== undefined) plan.name = payload.name
      if (payload.description !== undefined) plan.description = payload.description
      await plan.save()

      if (payload.steps !== undefined) {
        // Remplace tout le set
        await PlanStep.query({ client: trx }).where('plan_id', plan.id).delete()
        await PlanStep.createMany(
          payload.steps.map((s) => ({
            planId: plan.id,
            order: s.order,
            offsetDays: s.offsetDays,
            tone: s.tone,
            subject: s.subject,
            body: s.body,
            requiresManualValidation: s.requiresManualValidation,
          })),
          { client: trx }
        )
      }
    })

    await plan.load('steps')
    return response.json({ data: serializePlan(plan) })
  }

  /**
   * POST /plans — création d'un plan custom.
   *
   * Slug auto-généré depuis `name`, suffixé en cas de collision dans l'org.
   * Le plan custom n'est pas marqué `isDefault` — il peut être supprimé
   * (V2) sans toucher à la bibliothèque.
   */
  async store({ auth, request, response }: HttpContext) {
    const organizationId = requireOrgId(auth)
    const payload = await request.validateUsing(createPlanValidator)

    const baseSlug = slugify(payload.name)
    const slug = await nextAvailableSlug(organizationId, baseSlug)

    const plan = await db.transaction(async (trx) => {
      const created = await Plan.create(
        {
          organizationId,
          slug,
          name: payload.name,
          description: payload.description ?? '',
          isDefault: false,
        },
        { client: trx }
      )

      await PlanStep.createMany(
        payload.steps.map((s) => ({
          planId: created.id,
          order: s.order,
          offsetDays: s.offsetDays,
          tone: s.tone,
          subject: s.subject,
          body: s.body,
          requiresManualValidation: s.requiresManualValidation,
        })),
        { client: trx }
      )

      return created
    })

    await plan.load('steps')
    return response.status(201).json({ data: serializePlan(plan) })
  }
}
