<?php

declare(strict_types=1);

namespace Tests\Feature;

use App\Models\Company;
use App\Models\Plan;
use App\Models\Subscription;
use App\Models\User;
use Illuminate\Foundation\Testing\DatabaseTransactions;
use Illuminate\Support\Facades\Cache;
use PHPUnit\Framework\Attributes\Test;
use Tests\TestCase;

/**
 * #89 — Ciclo de vida SaaS: el comando subscriptions:update-status debe avanzar
 * la máquina de estados (trial/active vencida → grace → expired), scopeCurrent
 * debe agrupar su OR, y hasActiveSubscription debe honrar el período de gracia.
 */
class SubscriptionLifecycleTest extends TestCase
{
    use DatabaseTransactions;

    private Company $company;

    private Plan $plan;

    protected function setUp(): void
    {
        parent::setUp();
        $this->plan = Plan::factory()->create();
        $this->company = Company::factory()->create();
    }

    private function sub(array $attrs): Subscription
    {
        return Subscription::factory()->create(array_merge([
            'company_id' => $this->company->id,
            'plan_id' => $this->plan->id,
        ], $attrs));
    }

    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 active_past_ends_at_moves_to_grace_and_keeps_access(): void
    {
        $sub = $this->sub([
            'status' => Subscription::STATUS_ACTIVE,
            'is_active' => true,
            'ends_at' => now()->subDays(2),
            'grace_period_ends_at' => null,
        ]);

        $this->artisan('subscriptions:update-status')->assertExitCode(0);

        $sub->refresh();
        $this->assertSame(Subscription::STATUS_GRACE, $sub->status);
        // Gracia contada desde ends_at: now-2 + 7 = now+5 (futuro).
        $this->assertTrue($sub->grace_period_ends_at->isFuture());
        $this->assertTrue($this->gateAllows(), 'Durante la gracia el acceso debe concederse.');
    }

    #[Test]
    public function grace_past_window_expires_and_revokes_access(): void
    {
        $this->sub([
            'status' => Subscription::STATUS_GRACE,
            'is_active' => true,
            'ends_at' => now()->subDays(10),
            'grace_period_ends_at' => now()->subDay(),
        ]);

        $this->artisan('subscriptions:update-status')->assertExitCode(0);

        $sub = $this->company->subscriptions()->first();
        $this->assertSame(Subscription::STATUS_EXPIRED, $sub->status);
        $this->assertFalse((bool) $sub->is_active);
        $this->assertFalse($this->gateAllows(), 'Tras expirar la gracia, sin acceso.');
    }

    #[Test]
    public function long_expired_active_goes_straight_to_expired_in_one_run(): void
    {
        // Vencida hace 30 días: la ventana de gracia (ends_at+7 = hace 23 días) ya
        // lapsó, así que en una sola pasada debe quedar EXPIRED, no resucitar.
        $this->sub([
            'status' => Subscription::STATUS_ACTIVE,
            'is_active' => true,
            'ends_at' => now()->subDays(30),
            'grace_period_ends_at' => null,
        ]);

        $this->artisan('subscriptions:update-status')->assertExitCode(0);

        $this->assertSame(Subscription::STATUS_EXPIRED, $this->company->subscriptions()->first()->status);
        $this->assertFalse($this->gateAllows());
    }

    #[Test]
    public function active_within_term_is_untouched(): void
    {
        $sub = $this->sub([
            'status' => Subscription::STATUS_ACTIVE,
            'is_active' => true,
            'ends_at' => now()->addMonth(),
        ]);

        $this->artisan('subscriptions:update-status')->assertExitCode(0);

        $sub->refresh();
        $this->assertSame(Subscription::STATUS_ACTIVE, $sub->status);
        $this->assertTrue($this->gateAllows());
    }

    #[Test]
    public function scope_current_excludes_canceled_with_future_grace_window(): void
    {
        // Regresión del OR sin agrupar: una suscripción CANCELADA con
        // grace_period_ends_at futuro NO debe contar como vigente.
        $this->sub([
            'status' => Subscription::STATUS_CANCELED,
            'is_active' => false,
            'ends_at' => now()->subDays(5),
            'grace_period_ends_at' => now()->addDays(30),
        ]);

        $this->assertFalse(
            $this->company->subscriptions()->active()->current()->exists(),
            'active()->current() no debe matchear una suscripción cancelada por su grace_period futuro.',
        );
        $this->assertFalse($this->gateAllows());
    }
}
