<?php

declare(strict_types=1);

namespace Database\Seeders;

use App\Enums\ContributionType;
use App\Enums\OwnershipModel;
use App\Models\Client;
use App\Models\Company;
use App\Models\Credit;
use App\Models\Installment;
use App\Models\Partner;
use App\Models\SupervisorCollector;
use App\Models\User;
use App\Services\CreditOperationService;
use App\Services\PartnerInvestmentService;
use App\Services\PaymentManager;
use App\Support\Demo;
use Illuminate\Database\Seeder;
use Illuminate\Support\Carbon;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\DB;

/**
 * Puebla la empresa demo compartida (flag `is_demo`) con un dataset realista:
 * ~20 clientes (con dirección principal) y 18 créditos en los 3 estados que
 * un visitante espera ver — al día, en mora y pagado — con sus cuotas, pagos
 * y movimientos financieros.
 *
 * SIEMPRE scoped a la empresa `is_demo=true`: resuelve la compañía y el
 * cobrador demo explícitamente por columna/email (la global scope de
 * MultiTenantScope es un NO-OP en consola) y nunca toca otra compañía.
 *
 * Reutiliza los servicios reales de la app — CreditOperationService::createNewCredit()
 * y PaymentManager::registerPaymentForCredit() — en vez de insertar filas a mano,
 * para que cuotas, auditoría y movimientos de caja sean idénticos a los que
 * genera la aplicación en producción.
 *
 * Determinista y repetible: pensado para ejecutarse tras cada reset de la
 * demo (ver credify:reset-demo), siempre sobre una empresa recién "vaciada".
 * Los `identification` de los clientes usan el prefijo fijo "DEMO-" para
 * garantizar que nunca choquen con la cédula (única en toda la app) de un
 * cliente real.
 */
class DemoDataSeeder extends Seeder
{
    /**
     * 20 nombres de clientes de muestra.
     *
     * @var array<int, string>
     */
    private const CLIENT_NAMES = [
        'Laura Restrepo', 'Miguel Ospina', 'Valentina Cárdenas', 'Andrés Pinzón',
        'Camila Rueda', 'Sebastián Mora', 'Daniela Peña', 'Julián Cárdenas',
        'Mariana Salcedo', 'Felipe Duarte', 'Natalia Cepeda', 'Esteban Rojas',
        'Paola Cifuentes', 'Ricardo Beltrán', 'Sofía Angulo', 'Diego Marín',
        'Adriana Trujillo', 'Camilo Zambrano', 'Isabella Correa', 'Santiago Ariza',
    ];

    public function run(): void
    {
        $demo = Company::withoutGlobalScopes()->where('is_demo', true)->first();

        if (! $demo) {
            $this->command->warn('DemoDataSeeder: no existe una empresa demo (is_demo=true). Ejecuta credify:setup-demo primero. Abortando.');

            return;
        }

        $collector = User::withoutGlobalScopes()
            ->where('company_id', $demo->id)
            ->where('email', Demo::ROLE_SLUG_TO_EMAIL['cobrador'])
            ->first();

        if (! $collector) {
            $this->command->warn('DemoDataSeeder: no existe el usuario cobrador demo. Ejecuta credify:setup-demo primero. Abortando.');

            return;
        }

        // El supervisor demo: se enlaza al cobrador para que la vista de supervisor
        // muestre su equipo y los datos (sin el enlace vería "sin cobradores").
        $supervisor = User::withoutGlobalScopes()
            ->where('company_id', $demo->id)
            ->where('email', Demo::ROLE_SLUG_TO_EMAIL['supervisor'])
            ->first();

        // El dueño demo: registrador del capital de los socios (atribución realista).
        $owner = User::withoutGlobalScopes()
            ->where('company_id', $demo->id)
            ->where('email', Demo::ROLE_SLUG_TO_EMAIL['dueno'])
            ->first();

        // CreditOperationService/CreditCalculator dependen de Auth::user() para
        // resolver company_id y para la auditoría — nos autenticamos temporalmente
        // como el cobrador demo (dueño real de estos datos) y restauramos el auth
        // previo al salir, incluso si algo falla.
        $previousUser = Auth::user();
        Auth::login($collector);

        try {
            DB::transaction(function () use ($demo, $collector, $supervisor, $owner) {
                $clients = $this->seedClients($demo, $collector);
                $this->seedCredits($collector, $clients);
                $this->seedTeam($demo, $supervisor, $collector);
                // Capital de los socios: sin el aporte inicial, los desembolsos
                // dejan la caja en negativo (un negocio de microcrédito arranca con
                // el capital de sus socios). Fechado antes que los créditos para que
                // el flujo de caja quede coherente (la caja se computa por fecha).
                $this->seedCapital($demo, $owner ?? $collector);
            });
        } finally {
            if ($previousUser) {
                Auth::login($previousUser);
            } else {
                Auth::logout();
            }
        }

        $this->command->info("DemoDataSeeder: dataset demo poblado para \"{$demo->name}\".");
    }

    /**
     * Crea los clientes demo (con su dirección principal), todos explícitamente
     * scoped a la empresa demo y atribuidos al cobrador demo.
     *
     * @return Collection<int, Client>
     */
    private function seedClients(Company $demo, User $collector): Collection
    {
        $clients = collect();

        foreach (self::CLIENT_NAMES as $index => $name) {
            $sequence = $index + 1;

            $client = Client::create([
                'company_id' => $demo->id,
                'name' => $name,
                // Prefijo fijo "DEMO-": la cédula es única en TODA la tabla (no solo
                // por compañía), así que nunca puede coincidir con un cliente real.
                'identification' => sprintf('DEMO-%06d', $sequence),
                'email' => "demo.cliente{$sequence}@credifygo.com",
                'phone' => '300'.str_pad((string) $sequence, 7, '0', STR_PAD_LEFT),
                'is_active' => true,
                'created_by_user_id' => $collector->id,
            ]);

            $client->addresses()->create([
                'address_line_1' => "Calle Demo #{$sequence}-".(($sequence % 20) + 1),
                'city' => 'Barranquilla',
                'state' => 'Atlántico',
                'is_primary' => true,
                'notes' => 'Cliente de datos demo (credify:reset-demo).',
            ]);

            $clients->push($client);
        }

        return $clients;
    }

    /**
     * Crea 18 créditos repartidos en 3 estados reales (6 cada uno):
     * al día, en mora y pagado. Usa los servicios reales de la app para que
     * las cuotas y los movimientos de caja sean indistinguibles de un
     * crédito creado a mano desde la PWA o Filament.
     *
     * @param  Collection<int, Client>  $clients
     */
    private function seedCredits(User $collector, Collection $clients): void
    {
        $creditService = app(CreditOperationService::class);
        $paymentManager = app(PaymentManager::class);
        $today = Carbon::today();

        // 6 créditos AL DÍA: desembolsados hace 2 días, primera cuota bien
        // adelante en el futuro → ninguna cuota vencida.
        foreach ($clients->slice(0, 6) as $i => $client) {
            $start = $today->copy()->subDays(2);

            $this->createCredit($creditService, $client, $collector, [
                'amount' => 300000 + ($i * 50000),
                'interest_rate' => 20,
                'installments_count' => 6,
                'periodicity' => 'monthly',
                'start_date' => $start,
                // Día del mes con margen amplio hacia adelante: garantiza que la
                // primera cuota vence varios días después de hoy.
                'due_day_1' => min(28, $start->day + 10),
            ]);
        }

        // 6 créditos EN MORA: desembolsados hace ~5 semanas con la primera cuota
        // el mismo día del desembolso → ya vencida hace semanas (delayed/overdue),
        // sin pasar el umbral de 90 días de "defaulted".
        foreach ($clients->slice(6, 6) as $i => $client) {
            $start = $today->copy()->subWeeks(5);

            $credit = $this->createCredit($creditService, $client, $collector, [
                'amount' => 400000 + ($i * 50000),
                'interest_rate' => 20,
                'installments_count' => 6,
                'periodicity' => 'monthly',
                'start_date' => $start,
                'due_day_1' => min(28, $start->day),
            ]);

            // Cobro parcial de la primera cuota: deja el crédito visiblemente "en
            // mora" en vez de completamente impago, más realista para una demo.
            $firstInstallment = Installment::where('credit_id', $credit->id)
                ->orderBy('due_date')
                ->first();

            if ($firstInstallment) {
                $partial = round(((float) $firstInstallment->total_amount) * 0.4, 2);
                $paymentManager->registerPaymentForInstallment(
                    $firstInstallment,
                    $partial,
                    $start->copy()->addDay(),
                    $collector->id,
                    'cash',
                );
            }
        }

        // 6 créditos PAGADOS: desembolsados hace ~6 semanas y liquidados en su
        // totalidad con un solo pago que cubre el saldo completo. OJO: debe
        // quedarse por debajo del umbral de 90 días de "defaulted" — ese estado
        // es INMUTABLE (Credit::canAutoUpdateStatus()) y un crédito que llega a
        // "defaulted" antes del pago ya nunca vuelve a auto-sincronizar a "paid".
        foreach ($clients->slice(12, 6) as $i => $client) {
            $start = $today->copy()->subWeeks(6);

            $credit = $this->createCredit($creditService, $client, $collector, [
                'amount' => 350000 + ($i * 50000),
                'interest_rate' => 20,
                'installments_count' => 4,
                'periodicity' => 'monthly',
                'start_date' => $start,
                'due_day_1' => min(28, $start->day),
            ]);

            $paymentManager->registerPaymentForCredit(
                $credit,
                (float) $credit->total_receivable,
                $today->copy()->subWeeks(2),
                $collector->id,
                'cash',
            );
        }
    }

    /**
     * Enlaza el supervisor demo con el cobrador demo (pivot supervisor_collector)
     * para que la vista de supervisor muestre a su equipo y sus métricas. Sin
     * este enlace el supervisor entra a un dashboard vacío ("sin cobradores").
     * Idempotente por la unique (company_id, supervisor_id, collector_id).
     */
    private function seedTeam(Company $demo, ?User $supervisor, User $collector): void
    {
        if (! $supervisor) {
            return;
        }

        SupervisorCollector::firstOrCreate([
            'company_id' => $demo->id,
            'supervisor_id' => $supervisor->id,
            'collector_id' => $collector->id,
        ]);
    }

    /**
     * Siembra 2 socios (60/40) con su aporte de capital inicial (~$20M) usando el
     * servicio real (PartnerInvestmentService), que crea el Income de capital
     * (affects_capital) → la caja arranca positiva y el patrimonio refleja el
     * capital + la cartera, como en un negocio real. Sin esto, los desembolsos
     * dejaban la caja en negativo. El aporte se atribuye al dueño (registrador).
     */
    private function seedCapital(Company $demo, User $registrar): void
    {
        $service = app(PartnerInvestmentService::class);
        $joinedAt = Carbon::today()->subWeeks(8);

        $partners = [
            ['name' => 'Inversión Familiar', 'pct' => '60.0000', 'amount' => '12000000'],
            ['name' => 'Socio Capital', 'pct' => '40.0000', 'amount' => '8000000'],
        ];

        // El servicio atribuye el aporte a auth()->id(): nos autenticamos como el
        // dueño para el registro y restauramos el usuario previo (el cobrador que
        // usan los créditos) al terminar, incluso si algo falla.
        $previousUser = Auth::user();
        Auth::login($registrar);

        try {
            foreach ($partners as $p) {
                $partner = Partner::create([
                    'company_id' => $demo->id,
                    'name' => $p['name'],
                    'ownership_model' => OwnershipModel::FIXED,
                    'fixed_ownership_percentage' => $p['pct'],
                    'status' => 'active',
                    'joined_at' => $joinedAt,
                ]);

                $service->registerContribution(
                    $partner,
                    $p['amount'],
                    ContributionType::INITIAL,
                    $joinedAt,
                    'cash',
                    null,
                    'Capital inicial (datos demo).',
                );
            }
        } finally {
            if ($previousUser) {
                Auth::login($previousUser);
            }
        }
    }

    /**
     * @param  array<string, mixed>  $overrides
     */
    private function createCredit(
        CreditOperationService $service,
        Client $client,
        User $collector,
        array $overrides,
    ): Credit {
        return $service->createNewCredit(array_merge([
            'company_id' => $client->company_id,
            'client_id' => $client->id,
            'collector_user_id' => $collector->id,
        ], $overrides));
    }
}
