<?php

use App\Jobs\SendPaymentFailedReminderJob;
use App\Listeners\HandleStripeWebhook;
use App\Models\Subscription;
use App\Models\User;
use App\Models\UserNotificationPreference;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\Queue;
use Laravel\Cashier\Events\WebhookReceived;

uses(RefreshDatabase::class);

it('busts the plan cache on customer.subscription.created', function (): void {
    $user = User::factory()->create(['stripe_id' => 'cus_created_1']);
    Cache::tags(['plans'])->put("plan_for_user_{$user->id}", 'stale', 3600);

    (new HandleStripeWebhook)->handle(new WebhookReceived([
        'type' => 'customer.subscription.created',
        'data' => ['object' => ['customer' => 'cus_created_1']],
    ]));

    expect(Cache::tags(['plans'])->get("plan_for_user_{$user->id}"))->toBeNull();
});

it('ignores subscription.created when the user is not found', function (): void {
    (new HandleStripeWebhook)->handle(new WebhookReceived([
        'type' => 'customer.subscription.created',
        'data' => ['object' => ['customer' => 'cus_unknown']],
    ]));

    expect(true)->toBeTrue();
});

it('busts the plan cache on customer.subscription.updated', function (): void {
    $user = User::factory()->create(['stripe_id' => 'cus_updated_1']);
    Cache::tags(['plans'])->put("plan_for_user_{$user->id}", 'stale', 3600);

    (new HandleStripeWebhook)->handle(new WebhookReceived([
        'type' => 'customer.subscription.updated',
        'data' => ['object' => ['customer' => 'cus_updated_1']],
    ]));

    expect(Cache::tags(['plans'])->get("plan_for_user_{$user->id}"))->toBeNull();
});

it('on customer.subscription.deleted disables whatsapp+sms prefs, clears grace, busts cache', function (): void {
    $user = User::factory()->create([
        'stripe_id' => 'cus_deleted_1',
        'grace_period_until' => now()->addDays(5),
    ]);

    UserNotificationPreference::query()->insert([
        ['user_id' => $user->id, 'channel' => 'whatsapp', 'fuel_type' => 'E10', 'enabled' => true, 'created_at' => now(), 'updated_at' => now()],
        ['user_id' => $user->id, 'channel' => 'sms',      'fuel_type' => 'E10', 'enabled' => true, 'created_at' => now(), 'updated_at' => now()],
        ['user_id' => $user->id, 'channel' => 'email',    'fuel_type' => 'E10', 'enabled' => true, 'created_at' => now(), 'updated_at' => now()],
    ]);

    Cache::tags(['plans'])->put("plan_for_user_{$user->id}", 'stale', 3600);

    (new HandleStripeWebhook)->handle(new WebhookReceived([
        'type' => 'customer.subscription.deleted',
        'data' => ['object' => ['customer' => 'cus_deleted_1']],
    ]));

    expect(UserNotificationPreference::where('user_id', $user->id)->where('channel', 'whatsapp')->value('enabled'))->toBeFalse();
    expect(UserNotificationPreference::where('user_id', $user->id)->where('channel', 'sms')->value('enabled'))->toBeFalse();
    expect(UserNotificationPreference::where('user_id', $user->id)->where('channel', 'email')->value('enabled'))->toBeTrue();
    expect($user->fresh()->grace_period_until)->toBeNull();
    expect(Cache::tags(['plans'])->get("plan_for_user_{$user->id}"))->toBeNull();
});

it('on invoice.payment_succeeded clears grace_period_until and busts cache', function (): void {
    $user = User::factory()->create([
        'stripe_id' => 'cus_paid_1',
        'grace_period_until' => now()->addDays(4),
    ]);
    Cache::tags(['plans'])->put("plan_for_user_{$user->id}", 'stale', 3600);

    (new HandleStripeWebhook)->handle(new WebhookReceived([
        'type' => 'invoice.payment_succeeded',
        'data' => ['object' => ['customer' => 'cus_paid_1']],
    ]));

    expect($user->fresh()->grace_period_until)->toBeNull();
    expect(Cache::tags(['plans'])->get("plan_for_user_{$user->id}"))->toBeNull();
});

it('invoice.payment_succeeded is a no-op when grace was never set', function (): void {
    $user = User::factory()->create(['stripe_id' => 'cus_paid_2', 'grace_period_until' => null]);

    (new HandleStripeWebhook)->handle(new WebhookReceived([
        'type' => 'invoice.payment_succeeded',
        'data' => ['object' => ['customer' => 'cus_paid_2']],
    ]));

    expect($user->fresh()->grace_period_until)->toBeNull();
});

it('on invoice.payment_failed sets grace_period_until 5 days out and queues both reminders', function (): void {
    Queue::fake();
    $user = User::factory()->create(['stripe_id' => 'cus_failed_1', 'grace_period_until' => null]);

    $before = now();
    (new HandleStripeWebhook)->handle(new WebhookReceived([
        'type' => 'invoice.payment_failed',
        'data' => ['object' => ['customer' => 'cus_failed_1']],
    ]));

    $grace = $user->fresh()->grace_period_until;
    expect($grace)->not->toBeNull();
    expect($grace->greaterThanOrEqualTo($before->copy()->addDays(5)->subSecond()))->toBeTrue();
    expect($grace->lessThanOrEqualTo($before->copy()->addDays(5)->addSeconds(5)))->toBeTrue();

    Queue::assertPushed(SendPaymentFailedReminderJob::class, function ($job) use ($user) {
        return $job->userId === $user->id && $job->day === 3;
    });
    Queue::assertPushed(SendPaymentFailedReminderJob::class, function ($job) use ($user) {
        return $job->userId === $user->id && $job->day === 5;
    });
    Queue::assertPushed(SendPaymentFailedReminderJob::class, 2);
});

it('persists current_period_start, current_period_end and stripe_data on subscription.updated', function (): void {
    $user = User::factory()->create(['stripe_id' => 'cus_period_1']);

    $user->subscriptions()->create([
        'type' => 'default',
        'stripe_id' => 'sub_period_1',
        'stripe_status' => 'active',
        'stripe_price' => 'price_plus_monthly',
        'quantity' => 1,
    ]);

    $start = 1714377600;
    $end = 1717056000;

    (new HandleStripeWebhook)->handle(new WebhookReceived([
        'type' => 'customer.subscription.updated',
        'data' => ['object' => [
            'id' => 'sub_period_1',
            'customer' => 'cus_period_1',
            'current_period_start' => $start,
            'current_period_end' => $end,
            'status' => 'active',
        ]],
    ]));

    $sub = Subscription::where('stripe_id', 'sub_period_1')->first();

    expect($sub->current_period_start->timestamp)->toBe($start);
    expect($sub->current_period_end->timestamp)->toBe($end);
    expect($sub->stripe_data)->toMatchArray(['id' => 'sub_period_1', 'status' => 'active']);
});

it('reads current_period_end from items.data[0] when not at the root (newer Stripe API)', function (): void {
    $user = User::factory()->create(['stripe_id' => 'cus_period_2']);

    $user->subscriptions()->create([
        'type' => 'default',
        'stripe_id' => 'sub_period_2',
        'stripe_status' => 'active',
        'stripe_price' => 'price_plus_monthly',
        'quantity' => 1,
    ]);

    $end = 1719648000;

    (new HandleStripeWebhook)->handle(new WebhookReceived([
        'type' => 'customer.subscription.updated',
        'data' => ['object' => [
            'id' => 'sub_period_2',
            'customer' => 'cus_period_2',
            'items' => ['data' => [['current_period_end' => $end]]],
        ]],
    ]));

    expect(Subscription::where('stripe_id', 'sub_period_2')->value('current_period_end')->timestamp)->toBe($end);
});

it('repeat invoice.payment_failed within grace does not re-dispatch reminders', function (): void {
    Queue::fake();
    $existingGrace = now()->addDays(3)->startOfSecond();
    $user = User::factory()->create(['stripe_id' => 'cus_failed_2', 'grace_period_until' => $existingGrace]);

    (new HandleStripeWebhook)->handle(new WebhookReceived([
        'type' => 'invoice.payment_failed',
        'data' => ['object' => ['customer' => 'cus_failed_2']],
    ]));

    // grace_period_until unchanged (same value)
    expect($user->fresh()->grace_period_until->equalTo($existingGrace))->toBeTrue();

    // No new jobs queued
    Queue::assertNothingPushed();
});
