<?php

declare(strict_types=1);

use App\Models\Affiliate;
use App\Models\AffiliateCommission;
use App\Models\AffiliatePayout;
use App\Models\AffiliateReferral;
use App\Models\PaymentTransaction;
use App\Models\User;
use App\Services\AffiliateService;
use Database\Seeders\RoleAndPermissionSeeder;

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

// --- Customer Affiliate Tests ---

it('allows a customer to apply for the affiliate program', function (): void {
    $user = User::factory()->customer()->create();

    $this->actingAs($user)
        ->post($this->accountUrl.'/affiliate/apply')
        ->assertRedirect();

    expect(Affiliate::where('user_id', $user->id)->exists())->toBeTrue();

    $affiliate = Affiliate::where('user_id', $user->id)->first();
    expect($affiliate->status)->toBe('pending');
    expect($affiliate->referral_code)->not->toBeEmpty();
});

it('prevents duplicate affiliate applications', function (): void {
    $user = User::factory()->customer()->create();
    Affiliate::factory()->create(['user_id' => $user->id]);

    $this->actingAs($user)
        ->post($this->accountUrl.'/affiliate/apply')
        ->assertRedirect();

    expect(Affiliate::where('user_id', $user->id)->count())->toBe(1);
});

it('shows affiliate dashboard to active affiliate', function (): void {
    $user = User::factory()->customer()->create();
    Affiliate::factory()->active()->create(['user_id' => $user->id]);

    $this->actingAs($user)
        ->get($this->accountUrl.'/affiliate')
        ->assertOk()
        ->assertInertia(fn ($page) => $page
            ->component('Account/Affiliate/Dashboard')
            ->has('affiliate')
            ->has('stats')
        );
});

it('shows apply page when user has no affiliate', function (): void {
    $user = User::factory()->customer()->create();

    $this->actingAs($user)
        ->get($this->accountUrl.'/affiliate')
        ->assertOk()
        ->assertInertia(fn ($page) => $page
            ->component('Account/Affiliate/Dashboard')
            ->where('affiliate', null)
        );
});

it('shows referrals for active affiliate', function (): void {
    $user = User::factory()->customer()->create();
    $affiliate = Affiliate::factory()->active()->create(['user_id' => $user->id]);
    AffiliateReferral::factory()->count(3)->create(['affiliate_id' => $affiliate->id]);

    $this->actingAs($user)
        ->get($this->accountUrl.'/affiliate/referrals')
        ->assertOk()
        ->assertInertia(fn ($page) => $page
            ->component('Account/Affiliate/Referrals')
            ->has('referrals.data', 3)
        );
});

it('shows commissions for active affiliate', function (): void {
    $user = User::factory()->customer()->create();
    $affiliate = Affiliate::factory()->active()->create(['user_id' => $user->id]);
    $referral = AffiliateReferral::factory()->create(['affiliate_id' => $affiliate->id]);
    AffiliateCommission::factory()->count(2)->create([
        'affiliate_id' => $affiliate->id,
        'referral_id' => $referral->id,
    ]);

    $this->actingAs($user)
        ->get($this->accountUrl.'/affiliate/commissions')
        ->assertOk()
        ->assertInertia(fn ($page) => $page
            ->component('Account/Affiliate/Commissions')
            ->has('commissions.data', 2)
        );
});

it('allows payout request when balance meets minimum', function (): void {
    $user = User::factory()->customer()->create();
    $affiliate = Affiliate::factory()->active()->create([
        'user_id' => $user->id,
        'pending_balance' => 100.00,
        'minimum_payout' => 50.00,
    ]);

    $this->actingAs($user)
        ->post($this->accountUrl.'/affiliate/payouts', ['method' => 'paypal'])
        ->assertRedirect();

    $payout = AffiliatePayout::where('affiliate_id', $affiliate->id)->first();
    expect($payout)->not->toBeNull();
    expect((float) $payout->amount)->toBe(100.00);
    expect($payout->method)->toBe('paypal');
    expect($payout->status)->toBe('pending');

    $affiliate->refresh();
    expect((float) $affiliate->pending_balance)->toBe(0.00);
});

it('rejects payout request when balance is below minimum', function (): void {
    $user = User::factory()->customer()->create();
    Affiliate::factory()->active()->create([
        'user_id' => $user->id,
        'pending_balance' => 20.00,
        'minimum_payout' => 50.00,
    ]);

    $this->actingAs($user)
        ->post($this->accountUrl.'/affiliate/payouts', ['method' => 'paypal'])
        ->assertRedirect();

    expect(AffiliatePayout::count())->toBe(0);
});

// --- Admin Affiliate Tests ---

it('admin can view affiliate list', function (): void {
    $admin = User::factory()->admin()->create();
    Affiliate::factory()->count(3)->create();

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

it('admin can view affiliate detail', function (): void {
    $admin = User::factory()->admin()->create();
    $affiliate = Affiliate::factory()->active()->create();

    $this->actingAs($admin)
        ->get($this->adminUrl."/affiliates/{$affiliate->id}")
        ->assertOk()
        ->assertInertia(fn ($page) => $page
            ->component('Admin/Affiliates/Show')
            ->has('affiliate')
        );
});

it('admin can approve pending affiliate', function (): void {
    $admin = User::factory()->admin()->create();
    $affiliate = Affiliate::factory()->pending()->create();

    $this->actingAs($admin)
        ->post($this->adminUrl."/affiliates/{$affiliate->id}/approve")
        ->assertRedirect();

    $affiliate->refresh();
    expect($affiliate->status)->toBe('active');
    expect($affiliate->approved_at)->not->toBeNull();
});

it('admin can suspend active affiliate', function (): void {
    $admin = User::factory()->admin()->create();
    $affiliate = Affiliate::factory()->active()->create();

    $this->actingAs($admin)
        ->post($this->adminUrl."/affiliates/{$affiliate->id}/suspend")
        ->assertRedirect();

    $affiliate->refresh();
    expect($affiliate->status)->toBe('suspended');
});

it('admin can approve pending commission', function (): void {
    $admin = User::factory()->admin()->create();
    $affiliate = Affiliate::factory()->active()->create(['total_earned' => 0]);
    $referral = AffiliateReferral::factory()->create(['affiliate_id' => $affiliate->id]);
    $commission = AffiliateCommission::factory()->create([
        'affiliate_id' => $affiliate->id,
        'referral_id' => $referral->id,
        'amount' => 25.00,
        'status' => 'pending',
    ]);

    $this->actingAs($admin)
        ->post($this->adminUrl."/affiliate-commissions/{$commission->id}/approve")
        ->assertRedirect();

    $commission->refresh();
    expect($commission->status)->toBe('approved');
    expect($commission->approved_at)->not->toBeNull();

    $affiliate->refresh();
    expect((float) $affiliate->total_earned)->toBe(25.00);
});

it('admin can process payout', function (): void {
    $admin = User::factory()->admin()->create();
    $affiliate = Affiliate::factory()->active()->create(['total_paid' => 0]);
    $payout = AffiliatePayout::factory()->create([
        'affiliate_id' => $affiliate->id,
        'amount' => 75.00,
        'status' => 'pending',
    ]);

    $this->actingAs($admin)
        ->post($this->adminUrl."/affiliate-payouts/{$payout->id}/process")
        ->assertRedirect();

    $payout->refresh();
    expect($payout->status)->toBe('completed');
    expect($payout->processed_at)->not->toBeNull();
    expect($payout->reference)->not->toBeNull();

    $affiliate->refresh();
    expect((float) $affiliate->total_paid)->toBe(75.00);
});

// --- AffiliateService Tests ---

it('AffiliateService::apply creates pending affiliate with unique referral code', function (): void {
    $user = User::factory()->create();
    $service = app(AffiliateService::class);

    $affiliate = $service->apply($user);

    expect($affiliate->status)->toBe('pending');
    expect($affiliate->user_id)->toBe($user->id);
    expect($affiliate->referral_code)->toHaveLength(8);
    expect(Affiliate::where('referral_code', $affiliate->referral_code)->count())->toBe(1);
});

it('AffiliateService::recordReferral creates referral record', function (): void {
    $service = app(AffiliateService::class);
    $affiliate = Affiliate::factory()->active()->create();
    $referredUser = User::factory()->create();

    $referral = $service->recordReferral($affiliate, $referredUser, '192.168.1.1', 'https://example.com');

    expect($referral->affiliate_id)->toBe($affiliate->id);
    expect($referral->referred_user_id)->toBe($referredUser->id);
    expect($referral->signup_ip)->toBe('192.168.1.1');
    expect($referral->status)->toBe('pending');
});

it('AffiliateService::recordCommission calculates percentage correctly', function (): void {
    $service = app(AffiliateService::class);
    $affiliate = Affiliate::factory()->active()->create([
        'commission_type' => 'percentage',
        'commission_rate' => 10.00,
        'pending_balance' => 0,
    ]);
    $referral = AffiliateReferral::factory()->create(['affiliate_id' => $affiliate->id]);
    $transaction = PaymentTransaction::factory()->create(['amount' => 100.00]);

    $commission = $service->recordCommission($referral, $transaction, 'signup');

    expect((float) $commission->amount)->toBe(10.00);
    expect($commission->type)->toBe('signup');
    expect($commission->status)->toBe('pending');

    $affiliate->refresh();
    expect((float) $affiliate->pending_balance)->toBe(10.00);
});

it('AffiliateService::recordCommission calculates flat rate correctly', function (): void {
    $service = app(AffiliateService::class);
    $affiliate = Affiliate::factory()->flat()->create(['pending_balance' => 0]);
    $referral = AffiliateReferral::factory()->create(['affiliate_id' => $affiliate->id]);
    $transaction = PaymentTransaction::factory()->create(['amount' => 100.00]);

    $commission = $service->recordCommission($referral, $transaction, 'renewal');

    expect((float) $commission->amount)->toBe(5.00);
    expect($commission->type)->toBe('renewal');
});

it('Affiliate::referralUrl returns correct URL', function (): void {
    $affiliate = Affiliate::factory()->create(['referral_code' => 'testcode']);

    $url = $affiliate->referralUrl();

    expect($url)->toContain('ref=testcode');
    expect($url)->toContain(config('app.domains.marketing'));
});
