<?php

namespace Tests\Tenant;

use App\Facades\Settings;
use Tests\Data\FinanceSettingSeeder;
use Illuminate\Support\Collection;
use Mtc\MercuryDataModels\Dealership;
use Mtc\MercuryDataModels\Finance\Contracts\BatchRequestsQuotes;
use Mtc\MercuryDataModels\Finance\Contracts\FinanceProvider;
use Mtc\MercuryDataModels\Finance\Contracts\FinanceRequestData;
use Mtc\MercuryDataModels\Finance\Contracts\FinanceResult;
use Mtc\MercuryDataModels\Finance\Contracts\FinanceResultCollection;
use Mtc\MercuryDataModels\Finance\Contracts\FinanceType;
use Mtc\MercuryDataModels\Finance\Jobs\RunQuoteRequest;
use Mtc\MercuryDataModels\Vehicle;
use Mtc\MercuryDataModels\VehicleFinance;
use Tests\TenantTestCase;

class RunQuoteRequestTest extends TenantTestCase
{
    private function seedFinanceSettings(): void
    {
        $this->seed(FinanceSettingSeeder::class);
        Settings::update('finance-codeweavers-enabled', true);
        Settings::update('finance-codeweavers-api_key', 'test-key');
        Settings::update('finance-codeweavers-username', 'test-user');
        Settings::update('finance-codeweavers-password', 'test-pass');
    }

    private function createMockProvider(Collection $returnQuotes): BatchRequestsQuotes&FinanceProvider
    {
        $provider = $this->createMock(MockFinanceProvider::class);
        $provider->method('batchRequest')->willReturn($returnQuotes);
        $provider->method('dealerIdDataField')->willReturn('finance_dealer_id');

        return $provider;
    }

    private function makeFinanceResult(float $monthlyPrice = 199.99, FinanceType $financeType = FinanceType::PCP): FinanceResult
    {
        return new FinanceResult(
            provider: 'codeweavers',
            finance_type: $financeType,
            monthly_price: $monthlyPrice,
            term: 60,
            number_of_payments: 59,
            total_deposit: 1000.00,
            apr: 9.9,
            first_payment: $monthlyPrice,
            final_payment: 5000.00,
            interest_rate: 4.9,
            apply_url: 'https://example.com/apply',
            product_name: 'PCP Finance',
            annual_mileage: 10000,
            cash_price: 15000.00,
            dealer_deposit: 0,
            customer_deposit: 1000.00,
            payable_amount: 14000.00,
            option_to_purchase_fee: 10.00,
            documentation_fee: 0,
        );
    }

    public function testHandleProcessesVehicleWithRegistrationNumber(): void
    {
        $this->seedFinanceSettings();

        $dealership = Dealership::factory()->create();
        $vehicle = Vehicle::factory()->create([
            'registration_number' => 'AB12CDE',
            'cap_id' => '12345',
            'price' => 15000,
            'dealership_id' => $dealership->id,
            'data' => [],
        ]);

        $result = $this->makeFinanceResult();
        $quotes = collect([
            $vehicle->registration_number => new FinanceResultCollection([$result]),
        ]);

        $provider = $this->createMockProvider($quotes);
        $provider->expects($this->once())
            ->method('batchRequest')
            ->with($this->callback(function (Collection $batch) use ($vehicle) {
                $item = $batch->first();
                return $item instanceof FinanceRequestData
                    && $item->registration_number === $vehicle->registration_number
                    && $item->cap_id === $vehicle->cap_id;
            }))
            ->willReturn($quotes);

        $job = new RunQuoteRequest($provider, true);
        $job->handle();

        $vehicle->refresh();
        $this->assertEquals(199.99, $vehicle->monthly_price);
        $this->assertEquals('pcp', $vehicle->monthly_cost_type);
        $this->assertEquals(1000.00, $vehicle->deposit);
        $this->assertCount(1, $vehicle->financeExamples);
        $this->assertEquals('codeweavers', $vehicle->financeExamples->first()->provider);
    }

    public function testHandleProcessesVehicleWithCapIdButNoRegistration(): void
    {
        $this->seedFinanceSettings();

        $dealership = Dealership::factory()->create();
        $vehicle = Vehicle::factory()->create([
            'registration_number' => null,
            'cap_id' => '67890',
            'price' => 20000,
            'is_new' => true,
            'dealership_id' => $dealership->id,
            'data' => [],
        ]);

        $key = (string) ($vehicle->uuid ?? $vehicle->id);
        $result = $this->makeFinanceResult(249.99);
        $quotes = collect([
            $key => new FinanceResultCollection([$result]),
        ]);

        $provider = $this->createMockProvider($quotes);
        $provider->expects($this->once())
            ->method('batchRequest')
            ->with($this->callback(function (Collection $batch) use ($vehicle) {
                $item = $batch->first();
                return $item instanceof FinanceRequestData
                    && $item->registration_number === null
                    && $item->cap_id === $vehicle->cap_id;
            }))
            ->willReturn($quotes);

        $job = new RunQuoteRequest($provider, true);
        $job->handle();

        $vehicle->refresh();
        $this->assertEquals(249.99, $vehicle->monthly_price);
        $this->assertCount(1, $vehicle->financeExamples);
    }

    public function testHandleSkipsVehicleWithNoRegistrationAndNoCapId(): void
    {
        $this->seedFinanceSettings();

        $dealership = Dealership::factory()->create();
        Vehicle::factory()->create([
            'registration_number' => null,
            'cap_id' => null,
            'price' => 10000,
            'dealership_id' => $dealership->id,
            'data' => [],
        ]);

        $provider = $this->createMockProvider(collect());
        $provider->expects($this->never())->method('batchRequest');

        $job = new RunQuoteRequest($provider, true);
        $job->handle();
    }

    public function testHandleSkipsVehicleWithEmptyRegistrationAndEmptyCapId(): void
    {
        $this->seedFinanceSettings();

        $dealership = Dealership::factory()->create();
        Vehicle::factory()->create([
            'registration_number' => '',
            'cap_id' => '',
            'price' => 10000,
            'dealership_id' => $dealership->id,
            'data' => [],
        ]);

        $provider = $this->createMockProvider(collect());
        $provider->expects($this->never())->method('batchRequest');

        $job = new RunQuoteRequest($provider, true);
        $job->handle();
    }

    public function testHandleSkipsVehicleWithNoPrice(): void
    {
        $this->seedFinanceSettings();

        $dealership = Dealership::factory()->create();
        Vehicle::factory()->create([
            'registration_number' => 'XY34FGH',
            'cap_id' => '11111',
            'price' => null,
            'dealership_id' => $dealership->id,
            'data' => [],
        ]);

        $provider = $this->createMockProvider(collect());
        $provider->expects($this->once())
            ->method('batchRequest')
            ->with($this->callback(fn (Collection $batch) => $batch->isEmpty()))
            ->willReturn(collect());

        $job = new RunQuoteRequest($provider, true);
        $job->handle();
    }

    public function testHandleSkipsVehicleWithStopFinanceSync(): void
    {
        $this->seedFinanceSettings();

        $dealership = Dealership::factory()->create();
        Vehicle::factory()->create([
            'registration_number' => 'ZZ99ABC',
            'cap_id' => '22222',
            'price' => 18000,
            'dealership_id' => $dealership->id,
            'data' => ['stop_finance_sync' => true],
        ]);

        $provider = $this->createMockProvider(collect());
        $provider->expects($this->once())
            ->method('batchRequest')
            ->with($this->callback(fn (Collection $batch) => $batch->isEmpty()))
            ->willReturn(collect());

        $job = new RunQuoteRequest($provider, true);
        $job->handle();
    }

    public function testHandleProcessesMixedVehiclesWithAndWithoutRegistration(): void
    {
        $this->seedFinanceSettings();

        $dealership = Dealership::factory()->create();

        $vehicleWithReg = Vehicle::factory()->create([
            'registration_number' => 'AB12CDE',
            'cap_id' => '11111',
            'price' => 15000,
            'dealership_id' => $dealership->id,
            'data' => [],
        ]);

        $vehicleWithoutReg = Vehicle::factory()->create([
            'registration_number' => null,
            'cap_id' => '22222',
            'price' => 20000,
            'is_new' => true,
            'dealership_id' => $dealership->id,
            'data' => [],
        ]);

        $vehicleWithReg2 = Vehicle::factory()->create([
            'registration_number' => 'XY34FGH',
            'cap_id' => '33333',
            'price' => 30000,
            'dealership_id' => $dealership->id,
            'data' => [],
        ]);

        $vehicleWithoutReg2 = Vehicle::factory()->create([
            'registration_number' => null,
            'cap_id' => '44444',
            'price' => 25000,
            'is_new' => true,
            'dealership_id' => $dealership->id,
            'data' => [],
        ]);

        // Key for no-reg vehicles matches RunQuoteRequest's keyBy logic: uuid ?? id
        $keyWithoutReg = (string) ($vehicleWithoutReg->uuid ?? $vehicleWithoutReg->id);
        $keyWithoutReg2 = (string) ($vehicleWithoutReg2->uuid ?? $vehicleWithoutReg2->id);

        $quotes = collect([
            $vehicleWithReg->registration_number => new FinanceResultCollection([
                $this->makeFinanceResult(199.99, FinanceType::PCP),
            ]),
            $keyWithoutReg => new FinanceResultCollection([
                $this->makeFinanceResult(249.99, FinanceType::HP),
            ]),
            $vehicleWithReg2->registration_number => new FinanceResultCollection([
                $this->makeFinanceResult(349.99, FinanceType::PCP),
            ]),
            $keyWithoutReg2 => new FinanceResultCollection([
                $this->makeFinanceResult(299.99, FinanceType::HP),
            ]),
        ]);

        $provider = $this->createMockProvider($quotes);
        $provider->expects($this->atLeastOnce())
            ->method('batchRequest')
            ->with($this->callback(function (Collection $batch) {
                foreach ($batch as $item) {
                    $this->assertInstanceOf(FinanceRequestData::class, $item);
                }
                return $batch->count() === 4;
            }))
            ->willReturn($quotes);

        $job = new RunQuoteRequest($provider, true);
        $job->handle();

        // Vehicle with reg "AB12CDE" gets 199.99 PCP
        $vehicleWithReg->refresh();
        $this->assertEquals(199.99, $vehicleWithReg->monthly_price);
        $this->assertEquals('pcp', $vehicleWithReg->monthly_cost_type);
        $this->assertCount(1, $vehicleWithReg->financeExamples);
        $this->assertEquals('codeweavers', $vehicleWithReg->financeExamples->first()->provider);

        // Vehicle without reg (cap_id 22222) gets 249.99 HP
        $vehicleWithoutReg->refresh();
        $this->assertEquals(249.99, $vehicleWithoutReg->monthly_price);
        $this->assertEquals('hp', $vehicleWithoutReg->monthly_cost_type);
        $this->assertCount(1, $vehicleWithoutReg->financeExamples);

        // Vehicle with reg "XY34FGH" gets 349.99 PCP
        $vehicleWithReg2->refresh();
        $this->assertEquals(349.99, $vehicleWithReg2->monthly_price);
        $this->assertEquals('pcp', $vehicleWithReg2->monthly_cost_type);
        $this->assertCount(1, $vehicleWithReg2->financeExamples);

        // Vehicle without reg (cap_id 44444) gets 299.99 HP
        $vehicleWithoutReg2->refresh();
        $this->assertEquals(299.99, $vehicleWithoutReg2->monthly_price);
        $this->assertEquals('hp', $vehicleWithoutReg2->monthly_cost_type);
        $this->assertCount(1, $vehicleWithoutReg2->financeExamples);
    }

    public function testHandleDeletesOldQuotesAndUpdatesVehicle(): void
    {
        $this->seedFinanceSettings();

        $dealership = Dealership::factory()->create();
        $vehicle = Vehicle::factory()->create([
            'registration_number' => 'MM11NNN',
            'cap_id' => '33333',
            'price' => 25000,
            'dealership_id' => $dealership->id,
            'data' => [],
        ]);

        // Pre-existing finance quotes
        VehicleFinance::factory()->create([
            'vehicle_id' => $vehicle->id,
            'provider' => 'codeweavers',
            'monthly_price' => 350,
        ]);
        VehicleFinance::factory()->create([
            'vehicle_id' => $vehicle->id,
            'provider' => 'manual',
            'monthly_price' => 300,
        ]);

        $hp = $this->makeFinanceResult(280.00, FinanceType::HP);
        $pcp = $this->makeFinanceResult(220.00, FinanceType::PCP);
        $quotes = collect([
            $vehicle->registration_number => new FinanceResultCollection([$hp, $pcp]),
        ]);

        $provider = $this->createMockProvider($quotes);

        $job = new RunQuoteRequest($provider, true);
        $job->handle();

        $vehicle->refresh();

        // Old codeweavers quote deleted, manual preserved, 2 new ones created
        $this->assertCount(3, $vehicle->financeExamples);
        $this->assertEquals(1, $vehicle->financeExamples->where('provider', 'manual')->count());
        $this->assertEquals(2, $vehicle->financeExamples->where('provider', 'codeweavers')->count());

        // Vehicle updated with lowest monthly price
        $this->assertEquals(220.00, $vehicle->monthly_price);
        $this->assertEquals('pcp', $vehicle->monthly_cost_type);
    }
}

/**
 * Combined interface for mocking a provider that implements both FinanceProvider and BatchRequestsQuotes
 */
abstract class MockFinanceProvider implements FinanceProvider, BatchRequestsQuotes
{
}
