<?php

declare(strict_types=1);

use App\Models\Plan;
use App\Models\Service;
use App\Models\User;
use App\Services\Billing\DunningService;
use Database\Seeders\RoleAndPermissionSeeder;
use Laravel\Cashier\Subscription;

beforeEach(function (): void {
    $this->seed(RoleAndPermissionSeeder::class);
});

it('suspends services for overdue subscriptions past the grace period', function (): void {
    $user = User::factory()->create();
    $plan = Plan::factory()->create();

    $subscription = Subscription::create([
        'user_id' => $user->id,
        'type' => 'default',
        'stripe_id' => 'sub_test_123',
        'stripe_status' => 'past_due',
        'stripe_price' => 'price_test_123',
        'plan_id' => $plan->id,
    ]);

    // Manually set updated_at to past the grace period
    $graceDays = config('billing.suspension.days_past_due_to_suspend');
    $subscription->update(['updated_at' => now()->subDays($graceDays + 1)]);

    $service = Service::factory()->create([
        'user_id' => $user->id,
        'subscription_id' => $subscription->id,
        'plan_id' => $plan->id,
        'status' => 'active',
    ]);

    $dunningService = app(DunningService::class);
    $count = $dunningService->suspendOverdueSubscriptions();

    expect($count)->toBe(1);
    expect($service->fresh()->status)->toBe('suspended');
});

it('does not suspend services within the grace period', function (): void {
    $user = User::factory()->create();
    $plan = Plan::factory()->create();

    $subscription = Subscription::create([
        'user_id' => $user->id,
        'type' => 'default',
        'stripe_id' => 'sub_test_456',
        'stripe_status' => 'past_due',
        'stripe_price' => 'price_test_456',
        'plan_id' => $plan->id,
    ]);

    $service = Service::factory()->create([
        'user_id' => $user->id,
        'subscription_id' => $subscription->id,
        'plan_id' => $plan->id,
        'status' => 'active',
    ]);

    $dunningService = app(DunningService::class);
    $count = $dunningService->suspendOverdueSubscriptions();

    expect($count)->toBe(0);
    expect($service->fresh()->status)->toBe('active');
});

it('terminates services that have been suspended too long', function (): void {
    $user = User::factory()->create();
    $terminateDays = config('billing.suspension.days_suspended_to_terminate');

    $service = Service::factory()->suspended()->create([
        'user_id' => $user->id,
        'suspended_at' => now()->subDays($terminateDays + 1),
    ]);

    $dunningService = app(DunningService::class);
    $count = $dunningService->terminateLongSuspendedSubscriptions();

    expect($count)->toBe(1);
    expect($service->fresh()->status)->toBe('terminated');
    expect($service->fresh()->auto_renew)->toBeFalse();
});

it('does not terminate recently suspended services', function (): void {
    $user = User::factory()->create();

    $service = Service::factory()->suspended()->create([
        'user_id' => $user->id,
        'suspended_at' => now()->subDay(),
    ]);

    $dunningService = app(DunningService::class);
    $count = $dunningService->terminateLongSuspendedSubscriptions();

    expect($count)->toBe(0);
    expect($service->fresh()->status)->toBe('suspended');
});

it('reactivates suspended services when subscription becomes active', function (): void {
    $user = User::factory()->create();
    $plan = Plan::factory()->create();

    $subscription = Subscription::create([
        'user_id' => $user->id,
        'type' => 'default',
        'stripe_id' => 'sub_test_789',
        'stripe_status' => 'active',
        'stripe_price' => 'price_test_789',
        'plan_id' => $plan->id,
    ]);

    $service = Service::factory()->suspended()->create([
        'user_id' => $user->id,
        'subscription_id' => $subscription->id,
        'plan_id' => $plan->id,
    ]);

    $dunningService = app(DunningService::class);
    $dunningService->reactivateServicesForSubscription($subscription);

    expect($service->fresh()->status)->toBe('active');
    expect($service->fresh()->suspended_at)->toBeNull();
});
