<?php

declare(strict_types=1);

namespace App\Console\Commands;

use App\Models\CapitalContribution;
use App\Models\Client;
use App\Models\CollectionVisit;
use App\Models\CollectorCreditOrder;
use App\Models\Company;
use App\Models\CompanyCollectorGoal;
use App\Models\Credit;
use App\Models\CreditAuditLog;
use App\Models\CreditRestructureLog;
use App\Models\DashboardSnapshot;
use App\Models\Expense;
use App\Models\FinancialOperation;
use App\Models\HeldPayment;
use App\Models\Income;
use App\Models\Installment;
use App\Models\ParSnapshot;
use App\Models\Partner;
use App\Models\PartnerWithdrawal;
use App\Models\Payment;
use App\Models\PaymentAuditLog;
use App\Models\ProfitDistribution;
use App\Models\PwaAuditLog;
use App\Models\SubscriptionRequest;
use App\Models\SupervisorCollector;
use App\Models\User;
use App\Support\Demo;
use App\Support\DemoCredentials;
use Database\Seeders\DemoDataSeeder;
use Illuminate\Console\Command;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Facades\DB;

/**
 * Vacía y repuebla la empresa demo compartida (flag `is_demo`) con un
 * dataset fresco. SIEMPRE opera SOLO sobre la empresa `is_demo=true`.
 *
 * SEGURIDAD (leer antes de tocar este archivo):
 *   - GUARD 1: aborta si no existe EXACTAMENTE una empresa `is_demo=true`.
 *     Nunca opera "a ciegas" sobre un id supuesto.
 *   - GUARD 2: sin `--force`, solo resetea si ALGÚN indicador de uso del
 *     plan (usuarios/créditos/clientes/volumen) alcanza `--threshold`
 *     (default 90%). Evita resets innecesarios en medio del día.
 *   - Todo el borrado ocurre dentro de una transacción y filtra SIEMPRE
 *     por `company_id = <id de la demo>` (o, para las 2 tablas hijas sin
 *     columna `company_id` propia — `client_addresses` y
 *     `profit_distribution_lines` — vía subquery al padre ya filtrado).
 *   - El allowlist de modelos a borrar EXCLUYE explícitamente Company,
 *     Subscription, User y CompanyFinancialSettings: la Company y su
 *     Subscription se preservan, los 3 usuarios demo se preservan por
 *     email (Demo::USERS), y los settings financieros se preservan.
 *   - `withoutGlobalScopes()` en cada query: MultiTenantScope ya es un
 *     NO-OP en consola, pero se declara explícito para que el filtro real
 *     de seguridad sea SIEMPRE el `where('company_id', $id)` visible aquí,
 *     nunca un scope implícito.
 */
class ResetDemoCompany extends Command
{
    protected $signature = 'credify:reset-demo {--force} {--dry-run} {--threshold=90}';

    protected $description = 'Vacía y repuebla la empresa demo compartida con un dataset fresco (uso: nightly --force, intra-día por --threshold).';

    /**
     * Modelos tenant (con columna `company_id` propia) a vaciar, en un orden
     * seguro de hijo→padre. Verificado contra las migraciones reales: todas
     * las FKs relevantes son `cascadeOnDelete()`/`nullOnDelete()`, así que
     * ningún orden rompería por constraint — este orden es, además,
     * explícito y auto-documentado.
     *
     * `HeldPayment` va primero: referencia `credit_id`/`payment_id` (ambos
     * `nullOnDelete()`) y a los usuarios captured_by/synced_by/resolved_by
     * (también `nullOnDelete()`), así que los cobros retenidos se vacían
     * antes de tocar créditos, pagos o usuarios.
     *
     * NO incluye (a propósito): Company, Subscription, User,
     * CompanyFinancialSettings — esos se PRESERVAN.
     *
     * @var array<int, class-string<Model>>
     */
    private const TENANT_MODELS = [
        HeldPayment::class,
        Payment::class,
        PaymentAuditLog::class,
        Income::class,
        Expense::class,
        FinancialOperation::class,
        CollectionVisit::class,
        CollectorCreditOrder::class,
        CompanyCollectorGoal::class,
        SupervisorCollector::class,
        CapitalContribution::class,
        PartnerWithdrawal::class,
        ProfitDistribution::class,
        Partner::class,
        ParSnapshot::class,
        DashboardSnapshot::class,
        PwaAuditLog::class,
        CreditAuditLog::class,
        CreditRestructureLog::class,
        Installment::class,
        Credit::class,
        Client::class,
        SubscriptionRequest::class,
    ];

    public function handle(): int
    {
        // ── GUARD 1: exactamente UNA empresa demo ──────────────────────────
        $demos = Company::withoutGlobalScopes()->where('is_demo', true)->get();

        if ($demos->count() !== 1) {
            $this->error(
                "Se esperaba exactamente 1 empresa is_demo=true, se encontraron {$demos->count()}. Abortando sin tocar nada."
            );

            return self::FAILURE;
        }

        $demo = $demos->first();
        $id = $demo->id;

        // ── GUARD 2: umbral de uso (se salta con --force) ──────────────────
        if (! $this->option('force')) {
            $threshold = (int) $this->option('threshold');
            $usage = $demo->getUsageStats();

            $reachedThreshold = collect($usage)
                ->contains(fn (array $metric) => ($metric['percentage'] ?? 0) >= $threshold);

            if (! $reachedThreshold) {
                $this->info(
                    "Ningún indicador de uso alcanza el umbral de {$threshold}%. No se resetea (usa --force para forzar)."
                );

                return self::SUCCESS;
            }
        }

        // ── --dry-run: solo reportar lo que se borraría ────────────────────
        if ($this->option('dry-run')) {
            $this->info("[dry-run] Empresa demo #{$id} — filas que se borrarían:");

            $clientIds = Client::withoutGlobalScopes()->where('company_id', $id)->pluck('id');
            $this->line('  client_addresses: '.DB::table('client_addresses')->whereIn('client_id', $clientIds)->count());

            $profitDistributionIds = ProfitDistribution::withoutGlobalScopes()->where('company_id', $id)->pluck('id');
            $this->line('  profit_distribution_lines: '.DB::table('profit_distribution_lines')->whereIn('profit_distribution_id', $profitDistributionIds)->count());

            foreach (self::TENANT_MODELS as $modelClass) {
                $count = $modelClass::withoutGlobalScopes()->where('company_id', $id)->count();
                $table = (new $modelClass)->getTable();
                $this->line("  {$table}: {$count}");
            }

            $nonDemoUsers = User::withoutGlobalScopes()
                ->where('company_id', $id)
                ->whereNotIn('email', array_keys(Demo::USERS))
                ->count();
            $this->line("  users (no-demo): {$nonDemoUsers}");

            return self::SUCCESS;
        }

        // ── BORRADO REAL, dentro de una transacción ────────────────────────
        DB::transaction(function () use ($id): void {
            self::wipeTenantData($id);

            // Usuarios NO demo de la empresa demo (defensivo: los 3 usuarios
            // fijos de Demo::USERS se preservan siempre).
            User::withoutGlobalScopes()
                ->where('company_id', $id)
                ->whereNotIn('email', array_keys(Demo::USERS))
                ->delete();
        });

        $this->info("Empresa demo #{$id} vaciada. Repoblando con DemoDataSeeder...");

        $this->call('db:seed', [
            '--class' => DemoDataSeeder::class,
            '--force' => true,
        ]);

        // Rota las contraseñas demo (credenciales filtradas expiran en ≤24 h).
        DemoCredentials::rotate();
        $this->info('Contraseñas demo rotadas.');

        $this->info('Reset de la empresa demo completado.');

        return self::SUCCESS;
    }

    /**
     * Borra TODA la data tenant de una empresa (allowlist + los 2 hijos sin
     * `company_id` propio), en orden seguro hijo→padre. NO toca Company,
     * Subscription, User ni CompanyFinancialSettings (esos se preservan) y NO
     * abre transacción (el llamador decide el envoltorio).
     *
     * Extraído de handle() para reutilizarlo en los tests, que necesitan dejar
     * la empresa demo compartida VACÍA antes de sembrar (la BD de dev/prod puede
     * tener la demo ya poblada; el seeder no es idempotente sobre `DEMO-*`).
     * El filtro real de seguridad sigue siendo el `where('company_id', $id)`.
     */
    public static function wipeTenantData(int $id): void
    {
        // Hijos SIN columna company_id propia: se borran vía subquery al padre,
        // YA filtrado por company_id=$id.
        $clientIds = Client::withoutGlobalScopes()->where('company_id', $id)->pluck('id');
        DB::table('client_addresses')->whereIn('client_id', $clientIds)->delete();

        $profitDistributionIds = ProfitDistribution::withoutGlobalScopes()->where('company_id', $id)->pluck('id');
        DB::table('profit_distribution_lines')->whereIn('profit_distribution_id', $profitDistributionIds)->delete();

        // Allowlist de modelos tenant, hijo→padre.
        foreach (self::TENANT_MODELS as $modelClass) {
            $modelClass::withoutGlobalScopes()->where('company_id', $id)->delete();
        }
    }
}
