<?php

namespace Tests\Tenant;

use App\Facades\Settings;
use Illuminate\Support\Facades\Queue;
use Mtc\MercuryDataModels\OfferType;
use Mtc\MercuryDataModels\Vehicle;
use Mtc\MercuryDataModels\VehicleMake;
use Mtc\MercuryDataModels\VehicleOffer;
use Tests\FilterSettingSeed;
use Tests\TenantTestCase;

class VehicleOfferFilterTest extends TenantTestCase
{
    #[\Override]
    protected function setUp(): void
    {
        parent::setUp();
        $this->seed();
        $this->seed(FilterSettingSeed::class);
        $this->withoutExceptionHandling();
    }

    public function testFeatureOffNoInjection(): void
    {
        Queue::fake();
        Vehicle::factory(5)->create(['is_published' => true]);
        VehicleOffer::factory()->create(['published' => true]);

        $response = $this->postJson(route('vehicles.index'));

        $response->assertStatus(200);
        $vehicleOffer = collect($response->json('results.data'))->firstWhere('type', 'vehicle-offer');
        $this->assertNull($vehicleOffer);
    }

    public function testFeatureOnButStockTypeNotMatchingDoesNotInject(): void
    {
        Queue::fake();
        $this->enableVehicleOffersFeature(stockType: 'new', maxPerPage: 2);

        // SRP context is used (is_new=0)
        Vehicle::factory(5)->create(['is_published' => true, 'is_new' => false]);
        VehicleOffer::factory()->create(['published' => true]);

        $response = $this->postJson(route('vehicles.index'), [
            'selections' => [
                ['type' => 'is_new', 'value' => 'used'],
            ],
        ]);

        $response->assertStatus(200);
        $vehicleOffer = collect($response->json('results.data'))->firstWhere('type', 'vehicle-offer');
        $this->assertNull($vehicleOffer);
    }

    public function testStockTypeUsedMatchesUsedSrpContext(): void
    {
        Queue::fake();
        $this->enableVehicleOffersFeature(stockType: 'used', maxPerPage: 2);

        $make = VehicleMake::factory()->create();
        Vehicle::factory(5)->create(['is_published' => true, 'is_new' => false, 'make_id' => $make->id]);
        $this->createOfferForMake($make);

        $response = $this->postJson(route('vehicles.index'), [
            'selections' => [
                ['type' => 'is_new', 'value' => 'used'],
            ],
        ]);

        $response->assertStatus(200);
        $vehicleOffer = collect($response->json('results.data'))->firstWhere('type', 'vehicle-offer');
        $this->assertNotNull($vehicleOffer);
    }

    public function testStockTypeNewMatchesNewSrpContext(): void
    {
        Queue::fake();
        $this->enableVehicleOffersFeature(stockType: 'new', maxPerPage: 2);

        $make = VehicleMake::factory()->create();
        Vehicle::factory(5)->create(['is_published' => true, 'is_new' => true, 'make_id' => $make->id]);
        $this->createOfferForMake($make);

        $response = $this->postJson(route('vehicles.index'), [
            'selections' => [
                ['type' => 'is_new', 'value' => 'new'],
            ],
        ]);

        $response->assertStatus(200);
        $vehicleOffer = collect($response->json('results.data'))->firstWhere('type', 'vehicle-offer');
        $this->assertNotNull($vehicleOffer);
    }

    public function testFeatureOnStockTypeBothInjectsOffer(): void
    {
        Queue::fake();
        $this->enableVehicleOffersFeature(stockType: 'both', maxPerPage: 2);

        $make = VehicleMake::factory()->create();
        Vehicle::factory(10)->create(['is_published' => true, 'make_id' => $make->id]);
        $offer = $this->createOfferForMake($make);

        $response = $this->postJson(route('vehicles.index'));

        $response->assertStatus(200);
        $data = $response->json('results.data');

        $vehicleOffer = collect($data)->firstWhere('type', 'vehicle-offer');
        $this->assertNotNull($vehicleOffer);
        $this->assertEquals($offer->id, $vehicleOffer['id']);

        // Never at position 0
        $this->assertNotEquals('vehicle-offer', $data[0]['type']);
    }

    public function testMakeFilterViaSelectionsRespectedOnVehicleOffer(): void
    {
        Queue::fake();
        $this->enableVehicleOffersFeature(stockType: 'both', maxPerPage: 5);

        $ford = VehicleMake::factory()->create();
        $other = VehicleMake::factory()->create();

        Vehicle::factory(5)->create([
            'is_published' => true,
            'make_id' => $ford->id,
        ]);

        $this->createOfferForMake($ford, ['name' => 'matching']);
        $this->createOfferForMake($other, ['name' => 'other']);

        $response = $this->postJson(route('vehicles.index'), [
            'selections' => [
                ['type' => 'make', 'value' => $ford->slug],
            ],
        ]);

        $response->assertStatus(200);
        $offers = collect($response->json('results.data'))->where('type', 'vehicle-offer')->values();
        $this->assertCount(1, $offers);
        $this->assertEquals('matching', $offers->first()['name']);
    }

    public function testMultipleMakeFiltersExcludeOffersForOtherMakes(): void
    {
        Queue::fake();
        $this->enableVehicleOffersFeature(stockType: 'both', maxPerPage: 5);

        $ford = VehicleMake::factory()->create();
        $fiat = VehicleMake::factory()->create();
        $mg = VehicleMake::factory()->create();

        Vehicle::factory(3)->create(['is_published' => true, 'make_id' => $ford->id]);
        Vehicle::factory(3)->create(['is_published' => true, 'make_id' => $fiat->id]);

        $this->createOfferForMake($ford, ['name' => 'ford-offer']);
        $this->createOfferForMake($fiat, ['name' => 'fiat-offer']);
        $this->createOfferForMake($mg, ['name' => 'mg-offer']);

        $response = $this->postJson(route('vehicles.index'), [
            'selections' => [
                ['type' => 'make', 'value' => $ford->slug],
                ['type' => 'make', 'value' => $fiat->slug],
            ],
        ]);

        $response->assertStatus(200);
        $offerNames = collect($response->json('results.data'))
            ->where('type', 'vehicle-offer')
            ->pluck('name')
            ->all();

        $this->assertNotContains('mg-offer', $offerNames);
    }

    public function testWeightedOffersOrderedByWeightOnPageOne(): void
    {
        Queue::fake();
        $this->enableVehicleOffersFeature(stockType: 'both', maxPerPage: 5, maxWeighted: 2);

        $make = VehicleMake::factory()->create();
        Vehicle::factory(15)->create(['is_published' => true, 'make_id' => $make->id]);

        $this->createOfferForMake($make, ['weight' => 1, 'name' => 'low']);
        $this->createOfferForMake($make, ['weight' => 5, 'name' => 'high']);
        $this->createOfferForMake($make, ['weight' => 0, 'name' => 'zero']);

        $response = $this->postJson(route('vehicles.index'));

        $response->assertStatus(200);
        $offers = collect($response->json('results.data'))->where('type', 'vehicle-offer')->values();

        $this->assertCount(2, $offers);
        $names = $offers->pluck('name')->toArray();
        $this->assertContains('low', $names);
        $this->assertContains('high', $names);
        $this->assertNotContains('zero', $names);
    }

    public function testNoAdjacencyWithOtherInjectedOffers(): void
    {
        Queue::fake();
        $this->enableVehicleOffersFeature(stockType: 'both', maxPerPage: 2);

        $make = VehicleMake::factory()->create();
        Vehicle::factory(20)->create(['is_published' => true, 'make_id' => $make->id]);
        $this->createOfferForMake($make);
        $this->createOfferForMake($make);

        $response = $this->postJson(route('vehicles.index'));

        $response->assertStatus(200);
        $data = $response->json('results.data');

        $positions = collect($data)
            ->keys()
            ->filter(fn ($key) => $data[$key]['type'] === 'vehicle-offer')
            ->values()
            ->toArray();

        $this->assertCount(2, $positions);
        $this->assertNotContains(0, $positions);
        $this->assertGreaterThan(1, abs($positions[0] - $positions[1]));
    }

    public function testPageTwoShowsRandomOffer(): void
    {
        Queue::fake();
        $this->enableVehicleOffersFeature(stockType: 'both', maxPerPage: 3);

        $make = VehicleMake::factory()->create();
        Vehicle::factory(30)->create(['is_published' => true, 'make_id' => $make->id]);
        $this->createOfferForMake($make);
        $this->createOfferForMake($make);
        $this->createOfferForMake($make);

        $response = $this->postJson(route('vehicles.index'), ['page' => 2]);

        $response->assertStatus(200);
        $offers = collect($response->json('results.data'))->where('type', 'vehicle-offer')->values();

        // Page 2+ shows only 1 random offer regardless of max-per-page
        $this->assertCount(1, $offers);
    }

    public function testOfferTypeFilterRestrictsInjection(): void
    {
        Queue::fake();
        $this->enableVehicleOffersFeature(stockType: 'both', maxPerPage: 5);

        $newCarType = OfferType::query()->create(['name' => 'New Car', 'slug' => 'new-car']);
        $motabilityType = OfferType::query()->create(['name' => 'Motability', 'slug' => 'motability']);

        $this->setOfferTypesFilter(['new-car']);

        $make = VehicleMake::factory()->create();
        Vehicle::factory(10)->create(['is_published' => true, 'make_id' => $make->id]);

        $this->createOfferForMake($make, ['type_id' => $newCarType->id, 'name' => 'new-car-offer']);
        $this->createOfferForMake($make, ['type_id' => $motabilityType->id, 'name' => 'motability-offer']);

        $response = $this->postJson(route('vehicles.index'));

        $response->assertStatus(200);
        $offerNames = collect($response->json('results.data'))
            ->where('type', 'vehicle-offer')
            ->pluck('name')
            ->all();

        $this->assertContains('new-car-offer', $offerNames);
        $this->assertNotContains('motability-offer', $offerNames);
    }

    public function testOrderBySettingUsesOrderField(): void
    {
        Queue::fake();
        $this->enableVehicleOffersFeature(stockType: 'both', maxPerPage: 2);
        $this->setOrderBy('order');

        $make = VehicleMake::factory()->create();
        Vehicle::factory(10)->create(['is_published' => true, 'make_id' => $make->id]);

        // Create three offers with distinct order values, weight = 0 on all
        // so we know sorting is driven by 'order' not 'weight'
        $this->createOfferForMake($make, ['order' => 3, 'weight' => 0, 'name' => 'third']);
        $this->createOfferForMake($make, ['order' => 1, 'weight' => 0, 'name' => 'first']);
        $this->createOfferForMake($make, ['order' => 2, 'weight' => 0, 'name' => 'second']);

        $response = $this->postJson(route('vehicles.index'));

        $response->assertStatus(200);
        $offerNames = collect($response->json('results.data'))
            ->where('type', 'vehicle-offer')
            ->pluck('name')
            ->all();

        $this->assertCount(2, $offerNames);
        $this->assertContains('first', $offerNames);
        $this->assertContains('second', $offerNames);
        $this->assertNotContains('third', $offerNames);
    }

    public function testOrderBySettingDefaultsToWeight(): void
    {
        Queue::fake();
        // Note: no setOrderBy call - default should still use weight (existing behaviour preserved)
        $this->enableVehicleOffersFeature(stockType: 'both', maxPerPage: 5, maxWeighted: 2);

        $make = VehicleMake::factory()->create();
        Vehicle::factory(10)->create(['is_published' => true, 'make_id' => $make->id]);

        $this->createOfferForMake($make, ['weight' => 1, 'order' => 3, 'name' => 'low-weight']);
        $this->createOfferForMake($make, ['weight' => 5, 'order' => 1, 'name' => 'high-weight']);
        $this->createOfferForMake($make, ['weight' => 0, 'order' => 2, 'name' => 'zero-weight']);

        $response = $this->postJson(route('vehicles.index'));

        $response->assertStatus(200);
        $offerNames = collect($response->json('results.data'))
            ->where('type', 'vehicle-offer')
            ->pluck('name')
            ->all();

        $this->assertCount(2, $offerNames);
        $this->assertContains('low-weight', $offerNames);
        $this->assertContains('high-weight', $offerNames);
        $this->assertNotContains('zero-weight', $offerNames);
    }

    private function enableVehicleOffersFeature(string $stockType, int $maxPerPage, int $maxWeighted = 0): void
    {
        Settings::make([
            'tab' => 'Automotive',
            'section' => 'SRP',
            'group' => 'Vehicle Offers',
            'name' => 'Enabled',
            'config_key' => 'automotive-srp-vehicle-offers-enabled',
            'type' => 'boolean',
            'value' => true,
        ]);

        Settings::make([
            'tab' => 'Automotive',
            'section' => 'SRP',
            'group' => 'Vehicle Offers',
            'name' => 'Stock type',
            'config_key' => 'automotive-srp-vehicle-offers-stock-type',
            'type' => 'string',
            'value' => $stockType,
        ]);

        Settings::make([
            'tab' => 'Automotive',
            'section' => 'SRP',
            'group' => 'Vehicle Offers',
            'name' => 'Max per page',
            'config_key' => 'automotive-srp-vehicle-offers-max-per-page',
            'type' => 'integer',
            'value' => $maxPerPage,
        ]);

        if ($maxWeighted > 0) {
            Settings::make([
                'tab' => 'Automotive',
                'section' => 'SRP',
                'group' => 'Vehicle Offers',
                'name' => 'Max weighted',
                'config_key' => 'vehicle-offer-max-weighted',
                'type' => 'integer',
                'value' => $maxWeighted,
            ]);
        }
    }

    /**
     * Create a VehicleOffer scoped to the given make with all other FK columns null,
     * so the offer is relevant to any vehicle of that make regardless of model, fuel type, etc.
     */
    private function createOfferForMake(VehicleMake $make, array $attributes = []): VehicleOffer
    {
        return VehicleOffer::factory()->create(array_merge([
            'published' => true,
            'make_id' => $make->id,
            'model_id' => null,
            'fuel_type_id' => null,
            'body_style_id' => null,
            'transmission_id' => null,
            'drivetrain_id' => null,
        ], $attributes));
    }

    private function setOfferTypesFilter(array $slugs): void
    {
        Settings::make([
            'tab' => 'Automotive',
            'section' => 'SRP',
            'group' => 'Vehicle Offers',
            'name' => 'Limit to offer types',
            'config_key' => 'automotive-srp-vehicle-offers-types',
            'type' => 'list',
            'source' => 'offer-types',
            'value' => $slugs,
        ]);
    }

    private function setOrderBy(string $value): void
    {
        Settings::make([
            'tab' => 'Automotive',
            'section' => 'SRP',
            'group' => 'Vehicle Offers',
            'name' => 'Order offers by',
            'config_key' => 'automotive-srp-vehicle-offers-order-by',
            'type' => 'string',
            'value' => $value,
        ]);
    }
}
