<?php

declare(strict_types=1);

use App\Events\SubscriptionCancelled;
use App\Events\SubscriptionCreated;
use App\Listeners\HandleSubscriptionCancelled;
use App\Listeners\HandleSubscriptionCreated;
use App\Models\CancellationSurvey;
use App\Models\User;
use App\Models\WinbackCampaign;
use App\Models\WinbackRecipient;
use App\Notifications\WinbackEmailNotification;
use Database\Seeders\RoleAndPermissionSeeder;
use Illuminate\Support\Facades\Notification;
use Laravel\Cashier\Subscription;

beforeEach(function (): void {
    $this->seed(RoleAndPermissionSeeder::class);
    $this->adminUrl = 'http://'.config('app.domains.admin');
    $this->marketingUrl = 'http://'.config('app.domains.marketing');
});

// ---------------------------------------------------------------------------
// Win-back enrollment on cancellation
// ---------------------------------------------------------------------------
describe('Win-back Enrollment', function (): void {
    it('enrolls user in matching campaign on cancellation', function (): void {
        Notification::fake();

        $user = User::factory()->customer()->create();
        $subscription = Subscription::factory()->create(['user_id' => $user->id]);
        $campaign = WinbackCampaign::factory()->active()->forReason('Too expensive')->create();

        $event = new SubscriptionCancelled($user, $subscription, 'Too expensive');
        $listener = app(HandleSubscriptionCancelled::class);
        $listener->handle($event);

        expect(WinbackRecipient::where('campaign_id', $campaign->id)->where('user_id', $user->id)->exists())
            ->toBeTrue();
    });

    it('does not create recipient when no matching campaign exists', function (): void {
        Notification::fake();

        $user = User::factory()->customer()->create();
        $subscription = Subscription::factory()->create(['user_id' => $user->id]);
        WinbackCampaign::factory()->active()->forReason('Missing features')->create();

        $event = new SubscriptionCancelled($user, $subscription, 'Too expensive');
        $listener = app(HandleSubscriptionCancelled::class);
        $listener->handle($event);

        expect(WinbackRecipient::where('user_id', $user->id)->exists())->toBeFalse();
    });

    it('uses catch-all campaign when reason is null', function (): void {
        Notification::fake();

        $user = User::factory()->customer()->create();
        $subscription = Subscription::factory()->create(['user_id' => $user->id]);
        $campaign = WinbackCampaign::factory()->active()->catchAll()->create();

        $event = new SubscriptionCancelled($user, $subscription, null);
        $listener = app(HandleSubscriptionCancelled::class);
        $listener->handle($event);

        expect(WinbackRecipient::where('campaign_id', $campaign->id)->where('user_id', $user->id)->exists())
            ->toBeTrue();
    });

    it('does not enroll in paused campaigns', function (): void {
        Notification::fake();

        $user = User::factory()->customer()->create();
        $subscription = Subscription::factory()->create(['user_id' => $user->id]);
        WinbackCampaign::factory()->paused()->forReason('Too expensive')->create();

        $event = new SubscriptionCancelled($user, $subscription, 'Too expensive');
        $listener = app(HandleSubscriptionCancelled::class);
        $listener->handle($event);

        expect(WinbackRecipient::where('user_id', $user->id)->exists())->toBeFalse();
    });
});

// ---------------------------------------------------------------------------
// Reactivation on new subscription
// ---------------------------------------------------------------------------
describe('Reactivation', function (): void {
    it('marks recipients as reactivated when user creates new subscription', function (): void {
        Notification::fake();

        $user = User::factory()->customer()->create();
        $subscription = Subscription::factory()->create(['user_id' => $user->id]);
        $campaign = WinbackCampaign::factory()->active()->create();
        $recipient = WinbackRecipient::factory()->create([
            'campaign_id' => $campaign->id,
            'user_id' => $user->id,
        ]);

        $event = new SubscriptionCreated($user, $subscription);
        $listener = app(HandleSubscriptionCreated::class);
        $listener->handle($event);

        $recipient->refresh();
        expect($recipient->reactivated)->toBeTrue();
        expect($recipient->reactivated_at)->not->toBeNull();
    });
});

// ---------------------------------------------------------------------------
// ProcessWinbackCampaigns command
// ---------------------------------------------------------------------------
describe('ProcessWinbackCampaigns Command', function (): void {
    it('sends scheduled emails', function (): void {
        Notification::fake();

        $campaign = WinbackCampaign::factory()->active()->create([
            'email_sequence' => [
                ['delay_days' => 1, 'subject' => 'We miss you', 'body' => 'Come back!'],
                ['delay_days' => 3, 'subject' => 'Special offer', 'body' => 'Here is a deal.'],
            ],
        ]);

        $user = User::factory()->customer()->create();
        $recipient = WinbackRecipient::factory()->create([
            'campaign_id' => $campaign->id,
            'user_id' => $user->id,
            'current_email_index' => 0,
            'last_email_sent_at' => null,
        ]);

        // Backdate the created_at using DB query to bypass Eloquent timestamp management
        \Illuminate\Support\Facades\DB::table('winback_recipients')
            ->where('id', $recipient->id)
            ->update(['created_at' => now()->subDays(2)]);

        $this->artisan('winback:process')->assertSuccessful();

        Notification::assertSentTo($user, WinbackEmailNotification::class);

        $recipient->refresh();
        expect($recipient->current_email_index)->toBe(1);
        expect($recipient->last_email_sent_at)->not->toBeNull();
    });

    it('skips unsubscribed recipients', function (): void {
        Notification::fake();

        $campaign = WinbackCampaign::factory()->active()->create([
            'email_sequence' => [
                ['delay_days' => 0, 'subject' => 'Test', 'body' => 'Test body'],
            ],
        ]);

        $user = User::factory()->customer()->create();
        WinbackRecipient::factory()->unsubscribed()->create([
            'campaign_id' => $campaign->id,
            'user_id' => $user->id,
            'current_email_index' => 0,
            'created_at' => now()->subDay(),
        ]);

        $this->artisan('winback:process')->assertSuccessful();

        Notification::assertNotSentTo($user, WinbackEmailNotification::class);
    });

    it('skips reactivated recipients', function (): void {
        Notification::fake();

        $campaign = WinbackCampaign::factory()->active()->create([
            'email_sequence' => [
                ['delay_days' => 0, 'subject' => 'Test', 'body' => 'Test body'],
            ],
        ]);

        $user = User::factory()->customer()->create();
        WinbackRecipient::factory()->reactivated()->create([
            'campaign_id' => $campaign->id,
            'user_id' => $user->id,
            'current_email_index' => 0,
            'created_at' => now()->subDay(),
        ]);

        $this->artisan('winback:process')->assertSuccessful();

        Notification::assertNotSentTo($user, WinbackEmailNotification::class);
    });

    it('skips recipients when delay has not elapsed', function (): void {
        Notification::fake();

        $campaign = WinbackCampaign::factory()->active()->create([
            'email_sequence' => [
                ['delay_days' => 7, 'subject' => 'Test', 'body' => 'Test body'],
            ],
        ]);

        $user = User::factory()->customer()->create();
        WinbackRecipient::factory()->create([
            'campaign_id' => $campaign->id,
            'user_id' => $user->id,
            'current_email_index' => 0,
            'created_at' => now(), // enrolled just now, need 7 days
        ]);

        $this->artisan('winback:process')->assertSuccessful();

        Notification::assertNotSentTo($user, WinbackEmailNotification::class);
    });
});

// ---------------------------------------------------------------------------
// Unsubscribe endpoint
// ---------------------------------------------------------------------------
describe('Unsubscribe', function (): void {
    it('unsubscribes recipient with valid signed URL', function (): void {
        $campaign = WinbackCampaign::factory()->active()->create();
        $user = User::factory()->create();
        $recipient = WinbackRecipient::factory()->create([
            'campaign_id' => $campaign->id,
            'user_id' => $user->id,
        ]);

        // Generate a signed URL using the marketing domain as the base
        // so the signature matches when we make the test request
        $signedUrl = \Illuminate\Support\Facades\URL::forceRootUrl($this->marketingUrl);

        $url = \Illuminate\Support\Facades\URL::signedRoute('winback.unsubscribe', [
            'recipient' => $recipient->id,
        ]);

        // Reset to default
        \Illuminate\Support\Facades\URL::forceRootUrl(null);

        $this->get($url)->assertOk();

        $recipient->refresh();
        expect($recipient->unsubscribed_at)->not->toBeNull();
    });

    it('rejects unsigned unsubscribe URL', function (): void {
        $campaign = WinbackCampaign::factory()->active()->create();
        $user = User::factory()->create();
        $recipient = WinbackRecipient::factory()->create([
            'campaign_id' => $campaign->id,
            'user_id' => $user->id,
        ]);

        $this->get($this->marketingUrl."/unsubscribe/winback/{$recipient->id}")
            ->assertForbidden();

        $recipient->refresh();
        expect($recipient->unsubscribed_at)->toBeNull();
    });
});

// ---------------------------------------------------------------------------
// Admin CRUD
// ---------------------------------------------------------------------------
describe('Admin Campaign CRUD', function (): void {
    it('allows admin to view campaigns index', function (): void {
        $admin = User::factory()->admin()->create();
        WinbackCampaign::factory()->count(3)->create();

        $this->actingAs($admin)
            ->get($this->adminUrl.'/winback-campaigns')
            ->assertOk()
            ->assertInertia(fn ($page) => $page
                ->component('Admin/WinbackCampaigns/Index')
                ->has('campaigns.data', 3)
            );
    });

    it('allows admin to create a campaign', function (): void {
        $admin = User::factory()->admin()->create();

        $this->actingAs($admin)
            ->post($this->adminUrl.'/winback-campaigns', [
                'name' => 'Test Win-back',
                'cancellation_reason' => 'Too expensive',
                'email_sequence' => [
                    ['delay_days' => 1, 'subject' => 'Come back!', 'body' => 'We miss you.'],
                ],
                'offer_type' => 'discount',
                'offer_value' => 20,
                'offer_duration_days' => 30,
                'coupon_code' => 'COMEBACK20',
                'status' => 'active',
            ])
            ->assertRedirect();

        expect(WinbackCampaign::where('name', 'Test Win-back')->exists())->toBeTrue();
    });

    it('allows admin to update a campaign', function (): void {
        $admin = User::factory()->admin()->create();
        $campaign = WinbackCampaign::factory()->create();

        $this->actingAs($admin)
            ->put($this->adminUrl."/winback-campaigns/{$campaign->id}", [
                'name' => 'Updated Campaign',
                'cancellation_reason' => null,
                'email_sequence' => [
                    ['delay_days' => 2, 'subject' => 'Updated subject', 'body' => 'Updated body.'],
                ],
                'offer_type' => 'none',
                'offer_value' => null,
                'offer_duration_days' => null,
                'coupon_code' => null,
                'status' => 'paused',
            ])
            ->assertRedirect();

        $campaign->refresh();
        expect($campaign->name)->toBe('Updated Campaign');
        expect($campaign->status)->toBe('paused');
    });

    it('allows admin to view campaign details with analytics', function (): void {
        $admin = User::factory()->admin()->create();
        $campaign = WinbackCampaign::factory()->create();
        WinbackRecipient::factory()->count(3)->create(['campaign_id' => $campaign->id]);

        $this->actingAs($admin)
            ->get($this->adminUrl."/winback-campaigns/{$campaign->id}")
            ->assertOk()
            ->assertInertia(fn ($page) => $page
                ->component('Admin/WinbackCampaigns/Show')
                ->has('campaign')
                ->has('recipients')
                ->has('analytics')
            );
    });

    it('allows admin to archive a campaign', function (): void {
        $admin = User::factory()->admin()->create();
        $campaign = WinbackCampaign::factory()->active()->create();

        $this->actingAs($admin)
            ->delete($this->adminUrl."/winback-campaigns/{$campaign->id}")
            ->assertRedirect();

        $campaign->refresh();
        expect($campaign->status)->toBe('archived');
    });

    it('validates email sequence requires at least one email', function (): void {
        $admin = User::factory()->admin()->create();

        $this->actingAs($admin)
            ->post($this->adminUrl.'/winback-campaigns', [
                'name' => 'Test',
                'email_sequence' => [],
                'offer_type' => 'none',
                'status' => 'active',
            ])
            ->assertSessionHasErrors(['email_sequence']);
    });

    it('prevents customer from accessing campaign pages', function (): void {
        $customer = User::factory()->customer()->create();

        $this->actingAs($customer)
            ->get($this->adminUrl.'/winback-campaigns')
            ->assertForbidden();
    });
});

// ---------------------------------------------------------------------------
// Admin Cancellation Surveys
// ---------------------------------------------------------------------------
describe('Admin Cancellation Surveys', function (): void {
    it('allows admin to view cancellation surveys', function (): void {
        $admin = User::factory()->admin()->create();
        CancellationSurvey::factory()->count(3)->create();

        $this->actingAs($admin)
            ->get($this->adminUrl.'/cancellation-surveys')
            ->assertOk()
            ->assertInertia(fn ($page) => $page
                ->component('Admin/CancellationSurveys/Index')
                ->has('surveys.data', 3)
                ->has('reasonBreakdown')
                ->has('monthlyTrend')
                ->has('totalSurveys')
                ->has('topReason')
                ->has('wouldReturnRate')
                ->has('availableReasons')
            );
    });

    it('filters surveys by reason', function (): void {
        $admin = User::factory()->admin()->create();
        CancellationSurvey::factory()->create(['cancellation_reason' => 'Too expensive']);
        CancellationSurvey::factory()->create(['cancellation_reason' => 'Missing features']);
        CancellationSurvey::factory()->create(['cancellation_reason' => 'Too expensive']);

        $this->actingAs($admin)
            ->get($this->adminUrl.'/cancellation-surveys?reason=Too+expensive')
            ->assertOk()
            ->assertInertia(fn ($page) => $page
                ->has('surveys.data', 2)
            );
    });
});
