<?php

declare(strict_types=1);

namespace Tests\Feature\Pwa;

use App\Models\Client;
use App\Models\CollectionVisit;
use App\Models\Company;
use App\Models\Credit;
use App\Models\Installment;
use App\Models\Payment;
use App\Models\Plan;
use App\Models\Subscription;
use App\Models\SupervisorCollector;
use App\Models\User;
use App\Services\PaymentManager;
use Carbon\Carbon;
use Illuminate\Foundation\Testing\DatabaseTransactions;
use Illuminate\Support\Facades\Event;
use Illuminate\Support\Str;
use Laravel\Sanctum\Sanctum;
use Spatie\Permission\Models\Role;
use Tests\TestCase;

/**
 * Tests de visibilidad: supervisores solo ven pagos y visitas de su equipo asignado.
 *
 * Bug: PaymentController::today() y CollectionVisitController::today() no filtraban
 * por el equipo del supervisor — exponían datos de toda la empresa.
 */
class SupervisorPaymentVisibilityTest extends TestCase
{
    use DatabaseTransactions;

    private Company $company;

    private Plan $plan;

    private User $supervisor;

    private User $collectorInTeam;

    private User $collectorOutside;

    protected function setUp(): void
    {
        parent::setUp();

        Event::fake(); // Evitar listeners asíncronos que puedan interferir

        // Crear roles necesarios para middleware pwa.access
        foreach (['admin', 'supervisor', 'collector'] as $role) {
            Role::firstOrCreate(['name' => $role, 'guard_name' => 'web']);
        }

        // Empresa con plan y suscripción activa
        $this->plan = Plan::factory()->create(['has_pwa_access' => true]);
        $this->company = Company::factory()->create();

        Subscription::factory()->active()->create([
            'company_id' => $this->company->id,
            'plan_id' => $this->plan->id,
            'ends_at' => now()->addYear(),
        ]);

        // Supervisor
        $this->supervisor = User::factory()->create([
            'company_id' => $this->company->id,
            'sees_all_collectors' => false,
        ]);
        $this->supervisor->assignRole('supervisor');
        $this->supervisor = $this->supervisor->fresh();

        // Collector asignado al supervisor
        $this->collectorInTeam = User::factory()->create([
            'company_id' => $this->company->id,
        ]);
        $this->collectorInTeam->assignRole('collector');
        $this->collectorInTeam = $this->collectorInTeam->fresh();

        // Collector NO asignado al supervisor (misma empresa)
        $this->collectorOutside = User::factory()->create([
            'company_id' => $this->company->id,
        ]);
        $this->collectorOutside->assignRole('collector');
        $this->collectorOutside = $this->collectorOutside->fresh();

        // Asignar collectorInTeam al supervisor usando el pivot SupervisorCollector
        SupervisorCollector::create([
            'company_id' => $this->company->id,
            'supervisor_id' => $this->supervisor->id,
            'collector_id' => $this->collectorInTeam->id,
        ]);
    }

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

    /**
     * Crea un crédito activo con una cuota pendiente asignado al collector dado.
     */
    private function makeCredit(User $collector): Credit
    {
        $client = Client::factory()->create(['company_id' => $this->company->id]);

        $credit = Credit::factory()->create([
            'company_id' => $this->company->id,
            'client_id' => $client->id,
            'collector_user_id' => $collector->id,
            'created_by_user_id' => $collector->id,
            'status' => Credit::STATUS_ACTIVE,
            'amount' => 100_000,
            'installments_count' => 1,
            'periodicity' => 'monthly',
            'start_date' => now()->subDays(5),
            'due_date' => now()->addDays(25),
        ]);

        Installment::factory()->create([
            'company_id' => $this->company->id,
            'credit_id' => $credit->id,
            'installment_number' => 1,
            'status' => Installment::STATUS_PENDING,
            'amount_paid' => 0,
            'total_amount' => 100_000,
            'principal_amount' => 80_000,
            'interest_amount' => 20_000,
            'principal_balance_after' => 100_000,
            'due_date' => now()->addDays(25),
        ]);

        return $credit;
    }

    /**
     * Registra un pago autenticando temporalmente como el collector dado.
     */
    private function makePayment(User $collector): Payment
    {
        Sanctum::actingAs($collector);

        $credit = $this->makeCredit($collector);

        return app(PaymentManager::class)->registerPayment(
            amount: 50_000.0,
            paymentDate: Carbon::today(),
            registeredByUserId: $collector->id,
            credit: $credit,
            paymentMethod: 'cash',
        );
    }

    /**
     * Crea una visita directamente en la base de datos para el collector dado.
     */
    private function makeVisit(User $collector): CollectionVisit
    {
        $credit = $this->makeCredit($collector);

        // Necesitamos bypassear MultiTenantScope autenticando al collector
        Sanctum::actingAs($collector);

        return CollectionVisit::create([
            'company_id' => $this->company->id,
            'credit_id' => $credit->id,
            'collector_user_id' => $collector->id,
            'visit_type' => CollectionVisit::TYPE_NO_PAYMENT,
            'reason' => CollectionVisit::REASON_NO_MONEY,
            'visit_date' => Carbon::today(),
            'idempotency_key' => Str::uuid()->toString(),
        ]);
    }

    // ------------------------------------------------------------------
    // Tests de pagos
    // ------------------------------------------------------------------

    /**
     * Un supervisor NO debe ver pagos de cobradores fuera de su equipo.
     */
    public function test_supervisor_cannot_see_payments_outside_their_team(): void
    {
        $payment = $this->makePayment($this->collectorOutside);

        // Ahora autenticar como supervisor
        Sanctum::actingAs($this->supervisor);

        $response = $this->getJson('/api/pwa/payments/today');

        $response->assertStatus(200);

        // El pago del cobrador externo NO debe aparecer
        $ids = collect($response->json('data'))->pluck('id')->all();
        $this->assertNotContains($payment->id, $ids, 'El supervisor no debe ver pagos de cobradores fuera de su equipo');
    }

    /**
     * Un supervisor SÍ debe ver pagos de cobradores de su equipo.
     */
    public function test_supervisor_can_see_team_payments(): void
    {
        $payment = $this->makePayment($this->collectorInTeam);

        // Autenticar como supervisor
        Sanctum::actingAs($this->supervisor);

        $response = $this->getJson('/api/pwa/payments/today');

        $response->assertStatus(200)
            ->assertJsonFragment(['id' => $payment->id]);
    }

    // ------------------------------------------------------------------
    // Tests de visitas
    // ------------------------------------------------------------------

    /**
     * Un supervisor NO debe ver visitas de cobradores fuera de su equipo.
     */
    public function test_supervisor_cannot_see_visits_outside_their_team(): void
    {
        $visit = $this->makeVisit($this->collectorOutside);

        // Autenticar como supervisor
        Sanctum::actingAs($this->supervisor);

        $response = $this->getJson('/api/pwa/visits/today');

        $response->assertStatus(200);

        // La visita del cobrador externo NO debe aparecer
        $ids = collect($response->json('data'))->pluck('id')->all();
        $this->assertNotContains($visit->id, $ids, 'El supervisor no debe ver visitas de cobradores fuera de su equipo');
    }

    /**
     * Un supervisor SÍ debe ver visitas de cobradores de su equipo.
     */
    public function test_supervisor_can_see_team_visits(): void
    {
        $visit = $this->makeVisit($this->collectorInTeam);

        // Autenticar como supervisor
        Sanctum::actingAs($this->supervisor);

        $response = $this->getJson('/api/pwa/visits/today');

        $response->assertStatus(200)
            ->assertJsonFragment(['id' => $visit->id]);
    }
}
