<?php

declare(strict_types=1);

namespace Tests\Feature;

use App\Models\Company;
use App\Models\Plan;
use App\Models\Subscription;
use App\Models\SubscriptionRequest;
use App\Models\User;
use App\Services\SubscriptionActivationService;
use Illuminate\Foundation\Testing\DatabaseTransactions;
use Illuminate\Support\Facades\Cache;
use PHPUnit\Framework\Attributes\Test;
use Spatie\Permission\Models\Role;
use Tests\TestCase;

/**
 * #163 — Aprobar una SubscriptionRequest debe ACTIVAR de verdad la suscripción
 * de la empresa (antes solo cambiaba el estado de la solicitud; había que editar
 * la Subscription a mano). El SubscriptionActivationService cierra el ciclo.
 */
class SubscriptionActivationTest extends TestCase
{
    use DatabaseTransactions;

    private Company $company;

    private User $approver;

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

        foreach (['admin', 'super_admin'] as $role) {
            Role::firstOrCreate(['name' => $role, 'guard_name' => 'web']);
        }

        $this->company = Company::factory()->create();
        $this->approver = User::factory()->create();
        $this->approver->assignRole('super_admin');
    }

    private function expiredSubscription(?int $planId = null): Subscription
    {
        return Subscription::factory()->create([
            'company_id' => $this->company->id,
            'plan_id' => $planId ?? Plan::firstWhere('slug', 'professional')?->id ?? Plan::factory()->create(['slug' => 'professional'])->id,
            'status' => Subscription::STATUS_EXPIRED,
            'is_active' => false,
            'billing_cycle' => Subscription::BILLING_MONTHLY,
            'ends_at' => now()->subDays(20),
            'grace_period_ends_at' => now()->subDays(13),
        ]);
    }

    private function pendingRequest(?int $requestedPlanId = null): SubscriptionRequest
    {
        return SubscriptionRequest::create([
            'company_id' => $this->company->id,
            'requested_by_user_id' => User::factory()->create(['company_id' => $this->company->id])->id,
            'request_type' => SubscriptionRequest::TYPE_UPGRADE_PLAN,
            'requested_plan_id' => $requestedPlanId,
            'comments' => 'Solicito activar mi suscripción.',
        ]);
    }

    private function gateAllows(): bool
    {
        Cache::forget(User::subscriptionCacheKey($this->company->id));
        $user = User::factory()->create(['company_id' => $this->company->id]);

        return $user->hasActiveSubscription();
    }

    #[Test]
    public function approving_a_request_reactivates_the_subscription_and_unblocks_access(): void
    {
        $subscription = $this->expiredSubscription();
        $request = $this->pendingRequest();

        $this->assertFalse($this->gateAllows(), 'Precondición: sin acceso (suscripción expirada).');

        $result = app(SubscriptionActivationService::class)
            ->approveAndActivate($request, $this->approver, 'Pago recibido por transferencia.');

        $this->assertSame($subscription->id, $result->id, 'Debe reactivar la suscripción existente, no crear otra.');
        $result->refresh();
        $this->assertSame(Subscription::STATUS_ACTIVE, $result->status);
        $this->assertTrue((bool) $result->is_active);
        $this->assertTrue($result->ends_at->isFuture());
        $this->assertNull($result->grace_period_ends_at);

        $this->assertSame(SubscriptionRequest::STATUS_IMPLEMENTED, $request->fresh()->status);
        $this->assertTrue($this->gateAllows(), 'Tras activar, la empresa recupera el acceso.');
    }

    #[Test]
    public function activation_extends_ends_at_by_one_month_for_a_monthly_cycle(): void
    {
        $this->expiredSubscription();
        $request = $this->pendingRequest();

        $result = app(SubscriptionActivationService::class)->approveAndActivate($request, $this->approver);

        // Expirada hace tiempo → se extiende desde "ahora" un ciclo mensual.
        $this->assertTrue($result->ends_at->between(now()->addDays(27), now()->addDays(32)));
    }

    #[Test]
    public function activation_applies_the_requested_plan(): void
    {
        $basic = Plan::firstWhere('slug', 'basic') ?? Plan::factory()->create(['slug' => 'basic']);
        $this->expiredSubscription(); // arranca en professional
        $request = $this->pendingRequest(requestedPlanId: $basic->id);

        $result = app(SubscriptionActivationService::class)->approveAndActivate($request, $this->approver);

        $this->assertSame($basic->id, $result->plan_id);
    }

    #[Test]
    public function marking_an_approved_request_implemented_also_activates_the_subscription(): void
    {
        // La otra mitad de #163: "Marcar Implementado" sobre una solicitud ya
        // aprobada (pero sin activar) también debe reactivar, no solo cambiar status.
        $subscription = $this->expiredSubscription();
        $request = $this->pendingRequest();
        $request->approve($this->approver->id, 'Aprobada, pendiente de implementar.');

        $this->assertFalse($this->gateAllows(), 'Precondición: aprobada pero aún sin acceso.');

        $result = app(SubscriptionActivationService::class)
            ->implementAndActivate($request, $this->approver, 'Pago confirmado.');

        $this->assertSame($subscription->id, $result->id);
        $result->refresh();
        $this->assertSame(Subscription::STATUS_ACTIVE, $result->status);
        $this->assertTrue($result->ends_at->isFuture());
        $this->assertSame(SubscriptionRequest::STATUS_IMPLEMENTED, $request->fresh()->status);
        $this->assertTrue($this->gateAllows(), 'Tras implementar, la empresa recupera el acceso.');
    }

    #[Test]
    public function activation_records_the_manual_payment(): void
    {
        // #165: billing manual/offline — al activar se registra cuánto pagó la
        // empresa y la referencia (informativo, no cobra nada).
        $this->expiredSubscription();
        $request = $this->pendingRequest();

        $result = app(SubscriptionActivationService::class)
            ->approveAndActivate($request, $this->approver, 'Pago recibido.', '249000', 'TRF-12345');

        $this->assertSame('249000.00', $result->amount_paid);
        $this->assertSame('TRF-12345', $result->payment_reference);
    }
}
