<?php

declare(strict_types=1);

namespace Tests\Feature;

use App\Exceptions\PlanLimitExceededException;
use App\Models\Client;
use App\Models\Company;
use App\Models\Credit;
use App\Models\Plan;
use App\Models\Subscription;
use App\Models\User;
use App\Services\PlanLimitService;
use Illuminate\Foundation\Testing\DatabaseTransactions;
use PHPUnit\Framework\Attributes\Test;
use Spatie\Permission\Models\Role;
use Tests\TestCase;

/**
 * Los límites del plan existían pero NUNCA se aplicaban en la creación. Estos tests
 * fijan el bloqueo estricto: al alcanzar el tope, crear otro registro se rechaza.
 */
class PlanLimitServiceTest extends TestCase
{
    use DatabaseTransactions;

    private PlanLimitService $service;

    protected function setUp(): void
    {
        parent::setUp();
        $this->service = app(PlanLimitService::class);
        Role::firstOrCreate(['name' => 'collector', 'guard_name' => 'web']);
    }

    /** @param array<string,int|float> $limits */
    private function companyOnPlan(array $limits): Company
    {
        $plan = Plan::factory()->create(array_merge([
            'max_users' => 0, 'max_collectors' => 0, 'max_active_credits' => 0,
            'max_clients' => 0, 'max_monthly_volume' => 0,
        ], $limits));

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

        return $company->refresh();
    }

    #[Test]
    public function blocks_adding_a_client_at_the_limit(): void
    {
        $company = $this->companyOnPlan(['max_clients' => 2]);
        Client::factory()->count(2)->create(['company_id' => $company->id]);

        $this->expectException(PlanLimitExceededException::class);
        $this->service->assertCanAddClient($company);
    }

    #[Test]
    public function allows_adding_a_client_under_the_limit(): void
    {
        $company = $this->companyOnPlan(['max_clients' => 2]);
        Client::factory()->create(['company_id' => $company->id]);

        $this->service->assertCanAddClient($company); // no lanza
        $this->assertTrue($company->canAddClient());
    }

    #[Test]
    public function blocks_adding_a_user_at_the_limit(): void
    {
        $company = $this->companyOnPlan(['max_users' => 2]);
        User::factory()->count(2)->create(['company_id' => $company->id]);

        $this->expectException(PlanLimitExceededException::class);
        $this->service->assertCanAddUser($company);
    }

    #[Test]
    public function blocks_adding_a_collector_at_the_limit(): void
    {
        $company = $this->companyOnPlan(['max_collectors' => 1]); // usuarios ilimitados, cobradores 1
        User::factory()->create(['company_id' => $company->id])->assignRole('collector');

        $this->expectException(PlanLimitExceededException::class);
        $this->service->assertCanAddCollector($company);
    }

    #[Test]
    public function blocks_a_new_credit_at_the_active_credit_limit(): void
    {
        $company = $this->companyOnPlan(['max_active_credits' => 1]);
        $client = Client::factory()->create(['company_id' => $company->id]);
        Credit::factory()->create([
            'company_id' => $company->id, 'client_id' => $client->id, 'status' => 'active',
        ]);

        $this->expectException(PlanLimitExceededException::class);
        $this->service->assertCanCreateCredit($company);
    }

    #[Test]
    public function blocks_a_credit_that_exceeds_monthly_volume(): void
    {
        $company = $this->companyOnPlan(['max_monthly_volume' => 1000000]); // créditos ilimitados, volumen 1M
        $client = Client::factory()->create(['company_id' => $company->id]);
        Credit::factory()->create([
            'company_id' => $company->id, 'client_id' => $client->id,
            'status' => 'active', 'amount' => 800000, 'created_at' => now(),
        ]);

        $this->expectException(PlanLimitExceededException::class);
        $this->service->assertCanCreateCredit($company, 300000.0); // 800k + 300k > 1M
    }

    #[Test]
    public function blocks_when_there_is_no_active_subscription(): void
    {
        $company = Company::factory()->create(); // sin suscripción

        $this->expectException(PlanLimitExceededException::class);
        $this->service->assertCanAddClient($company);
    }

    #[Test]
    public function guarded_create_is_atomic_and_rolls_back_when_the_assertion_fails(): void
    {
        // #169: runGuardedCreate corre el "chequear + crear" en una transacción con
        // lock por empresa. Si el assert falla DENTRO del closure, el create se revierte
        // (no queda registro a medias). Aquí simulamos: crear y luego exceder el cupo.
        $company = $this->companyOnPlan([]);

        $threw = false;
        try {
            $this->service->runGuardedCreate($company, function () use ($company) {
                Client::factory()->create(['company_id' => $company->id]); // se crea...
                throw PlanLimitExceededException::for('clientes', 1, 'Plan'); // ...pero el cupo falla
            });
        } catch (PlanLimitExceededException) {
            $threw = true;
        }

        $this->assertTrue($threw, 'la excepción de cupo se propaga');
        $this->assertSame(0, Client::where('company_id', $company->id)->count(), 'el create se revierte con la transacción');
    }

    #[Test]
    public function monthly_volume_excludes_transformation_children_and_canceled(): void
    {
        $company = $this->companyOnPlan(['max_monthly_volume' => 1000000]); // créditos ilimitados, volumen 1M
        $client = Client::factory()->create(['company_id' => $company->id]);

        // Desembolso real de este mes: 500k (raíz → cuenta).
        $root = Credit::factory()->create([
            'company_id' => $company->id, 'client_id' => $client->id,
            'status' => 'active', 'amount' => 500000, 'parent_credit_id' => null, 'created_at' => now(),
        ]);
        // Hijo de transformación 900k: saldo heredado, sin mover caja → NO cuenta (#172).
        Credit::factory()->create([
            'company_id' => $company->id, 'client_id' => $client->id,
            'status' => 'active', 'amount' => 900000, 'parent_credit_id' => $root->id, 'created_at' => now(),
        ]);
        // Crédito cancelado 900k: nunca se desembolsó → NO cuenta.
        Credit::factory()->create([
            'company_id' => $company->id, 'client_id' => $client->id,
            'status' => Credit::STATUS_CANCELED, 'amount' => 900000, 'parent_credit_id' => null, 'created_at' => now(),
        ]);

        $company->refresh();
        // Solo cuentan los 500k reales (no los 900k del hijo ni los 900k cancelados).
        $this->assertTrue($company->canDisburse(400000.0), '500k + 400k = 900k ≤ 1M → permitido');
        $this->assertFalse($company->canDisburse(600000.0), '500k + 600k = 1.1M > 1M → bloqueado');
    }
}
