<?php

namespace Tests\Tenant;

use App\Facades\Settings;
use Illuminate\Support\Facades\Queue;
use Mtc\MercuryDataModels\CatalogOffer;
use Mtc\MercuryDataModels\CatalogOfferRule;
use Mtc\MercuryDataModels\Dealership;
use Mtc\MercuryDataModels\Vehicle;
use Mtc\MercuryDataModels\VehicleMake;
use Tests\FilterSettingSeed;
use Tests\TenantTestCase;

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

        // Enable catalog offers injection
        Settings::make([
            'tab' => 'filter',
            'section' => 'filter',
            'group' => 'filter',
            'name' => 'Inject offers',
            'config_key' => 'filter-inject-offers-into-used-car-filter',
            'type' => 'boolean',
            'value' => true,
        ]);
    }

    public function testCatalogOfferWithNoRulesIsIncluded()
    {
        Queue::fake();
        Vehicle::factory(5)->create(['is_published' => true]);
        $offer = CatalogOffer::factory()->create(['active' => true]);

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

        $response->assertStatus(200);
        // Total should only count vehicles, not the injected catalog offer
        $this->assertEquals(5, $response->json('results.total'));
        // Data should contain 5 vehicles + 1 catalog offer
        $this->assertCount(6, $response->json('results.data'));

        $catalogOfferInResults = collect($response->json('results.data'))
            ->firstWhere('type', 'catalog-offer');
        $this->assertNotNull($catalogOfferInResults);
        $this->assertEquals($offer->id, $catalogOfferInResults['id']);
    }

    public function testCatalogOfferWithMatchingRulesIsIncluded()
    {
        Queue::fake();
        $make = VehicleMake::factory()->create();
        Vehicle::factory(5)->create([
            'is_published' => true,
            'make_id' => $make->id,
        ]);

        $offer = CatalogOffer::factory()->create(['active' => true]);
        CatalogOfferRule::query()->create([
            'catalog_offer_id' => $offer->id,
            'field' => 'make_id',
            'condition' => '=',
            'value' => $make->id,
        ]);

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

        $response->assertStatus(200);
        // Total should only count vehicles, not the injected catalog offer
        $this->assertEquals(5, $response->json('results.total'));
        // Data should contain 5 vehicles + 1 catalog offer
        $this->assertCount(6, $response->json('results.data'));

        $catalogOfferInResults = collect($response->json('results.data'))
            ->firstWhere('type', 'catalog-offer');
        $this->assertNotNull($catalogOfferInResults);
    }

    public function testCatalogOfferWithNonMatchingRulesIsExcluded()
    {
        Queue::fake();
        $make1 = VehicleMake::factory()->create();
        $make2 = VehicleMake::factory()->create();

        // All vehicles have make1
        Vehicle::factory(5)->create([
            'is_published' => true,
            'make_id' => $make1->id,
        ]);

        // Offer requires make2 which no vehicles have
        $offer = CatalogOffer::factory()->create(['active' => true]);
        CatalogOfferRule::query()->create([
            'catalog_offer_id' => $offer->id,
            'field' => 'make_id',
            'condition' => '=',
            'value' => $make2->id,
        ]);

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

        $response->assertStatus(200);
        // Only 5 vehicles, no catalog offer
        $this->assertEquals(5, $response->json('results.total'));

        $catalogOfferInResults = collect($response->json('results.data'))
            ->firstWhere('type', 'catalog-offer');
        $this->assertNull($catalogOfferInResults);
    }

    public function testCatalogOfferWithPriceRulesIsRespected()
    {
        Queue::fake();
        Vehicle::factory(3)->create([
            'is_published' => true,
            'price' => 15000,
        ]);
        Vehicle::factory(2)->create([
            'is_published' => true,
            'price' => 5000,
        ]);

        // Offer only for vehicles over 10000
        $offer = CatalogOffer::factory()->create(['active' => true]);
        CatalogOfferRule::query()->create([
            'catalog_offer_id' => $offer->id,
            'field' => 'price',
            'condition' => '>',
            'value' => '10000',
        ]);

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

        $response->assertStatus(200);
        // Total should only count vehicles, not the injected catalog offer
        $this->assertEquals(5, $response->json('results.total'));
        // Data should contain 5 vehicles + 1 catalog offer
        $this->assertCount(6, $response->json('results.data'));
    }

    public function testCatalogOfferWithMultipleRulesRequiresAllToMatch()
    {
        Queue::fake();
        $make = VehicleMake::factory()->create();

        // Vehicle matches both conditions
        Vehicle::factory(2)->create([
            'is_published' => true,
            'make_id' => $make->id,
            'price' => 15000,
        ]);

        // Vehicle matches only make condition
        Vehicle::factory(2)->create([
            'is_published' => true,
            'make_id' => $make->id,
            'price' => 5000,
        ]);

        // Offer requires make_id AND price > 10000
        $offer = CatalogOffer::factory()->create(['active' => true]);
        CatalogOfferRule::query()->create([
            'catalog_offer_id' => $offer->id,
            'field' => 'make_id',
            'condition' => '=',
            'value' => $make->id,
        ]);
        CatalogOfferRule::query()->create([
            'catalog_offer_id' => $offer->id,
            'field' => 'price',
            'condition' => '>',
            'value' => '10000',
        ]);

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

        $response->assertStatus(200);
        // Total should only count vehicles, not the injected catalog offer
        $this->assertEquals(4, $response->json('results.total'));
        // Data should contain 4 vehicles + 1 catalog offer
        $this->assertCount(5, $response->json('results.data'));
    }

    public function testCatalogOfferExcludedWhenMultipleRulesNotAllMatch()
    {
        Queue::fake();
        $make1 = VehicleMake::factory()->create();
        $make2 = VehicleMake::factory()->create();

        // Vehicles have make1 with high price
        Vehicle::factory(3)->create([
            'is_published' => true,
            'make_id' => $make1->id,
            'price' => 15000,
        ]);

        // Offer requires make2 AND high price - make2 doesn't exist
        $offer = CatalogOffer::factory()->create(['active' => true]);
        CatalogOfferRule::query()->create([
            'catalog_offer_id' => $offer->id,
            'field' => 'make_id',
            'condition' => '=',
            'value' => $make2->id,
        ]);
        CatalogOfferRule::query()->create([
            'catalog_offer_id' => $offer->id,
            'field' => 'price',
            'condition' => '>',
            'value' => '10000',
        ]);

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

        $response->assertStatus(200);
        // Only 3 vehicles, no catalog offer
        $this->assertEquals(3, $response->json('results.total'));
    }

    public function testCatalogOfferRespectsFilteredResults()
    {
        Queue::fake();
        $make1 = VehicleMake::factory()->create();
        $make2 = VehicleMake::factory()->create();

        // Vehicles with make1
        Vehicle::factory(3)->create([
            'is_published' => true,
            'make_id' => $make1->id,
        ]);

        // Vehicles with make2
        Vehicle::factory(3)->create([
            'is_published' => true,
            'make_id' => $make2->id,
        ]);

        // Offer matches make1
        $offer = CatalogOffer::factory()->create(['active' => true]);
        CatalogOfferRule::query()->create([
            'catalog_offer_id' => $offer->id,
            'field' => 'make_id',
            'condition' => '=',
            'value' => $make1->id,
        ]);

        // Filter for make2 - catalog offer should not appear since it targets make1
        $response = $this
            ->postJson(route('vehicles.index'), [
                'selections' => [
                    [
                        'type' => 'make',
                        'value' => $make2->slug,
                    ],
                ]
            ]);

        $response->assertStatus(200);
        // Only 3 vehicles (make2), no catalog offer since it requires make1
        $this->assertEquals(3, $response->json('results.total'));

        $catalogOfferInResults = collect($response->json('results.data'))
            ->firstWhere('type', 'catalog-offer');
        $this->assertNull($catalogOfferInResults);
    }

    public function testCatalogOfferAppearsWhenFilterMatchesOfferRules()
    {
        Queue::fake();
        $make = VehicleMake::factory()->create();

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

        // Some other vehicles
        Vehicle::factory(3)->create([
            'is_published' => true,
        ]);

        // Offer matches the make
        $offer = CatalogOffer::factory()->create(['active' => true]);
        CatalogOfferRule::query()->create([
            'catalog_offer_id' => $offer->id,
            'field' => 'make_id',
            'condition' => '=',
            'value' => $make->id,
        ]);

        // Filter for the same make - catalog offer should appear
        $response = $this
            ->postJson(route('vehicles.index'), [
                'selections' => [
                    [
                        'type' => 'make',
                        'value' => $make->slug,
                    ],
                ]
            ]);

        $response->assertStatus(200);
        // Total should only count vehicles, not the injected catalog offer
        $this->assertEquals(5, $response->json('results.total'));
        // Data should contain 5 vehicles + 1 catalog offer
        $this->assertCount(6, $response->json('results.data'));

        $catalogOfferInResults = collect($response->json('results.data'))
            ->firstWhere('type', 'catalog-offer');
        $this->assertNotNull($catalogOfferInResults);
    }

    public function testInactiveCatalogOfferIsNotIncluded()
    {
        Queue::fake();
        Vehicle::factory(5)->create(['is_published' => true]);
        CatalogOffer::factory()->create(['active' => false]);

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

        $response->assertStatus(200);
        $this->assertEquals(5, $response->json('results.total'));
    }

    public function testCatalogOffersDisabledSetting()
    {
        Queue::fake();
        // Disable catalog offers
        Settings::update('filter-inject-offers-into-used-car-filter', false);

        Vehicle::factory(5)->create(['is_published' => true]);
        CatalogOffer::factory()->create(['active' => true]);

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

        $response->assertStatus(200);
        $this->assertEquals(5, $response->json('results.total'));

        $catalogOfferInResults = collect($response->json('results.data'))
            ->firstWhere('type', 'catalog-offer');
        $this->assertNull($catalogOfferInResults);
    }

    public function testCatalogOfferWithInConditionMatchesMultipleDealerships()
    {
        Queue::fake();
        $dealership1 = Dealership::factory()->create();
        $dealership2 = Dealership::factory()->create();
        $dealership3 = Dealership::factory()->create();

        // Vehicles at dealership1 and dealership2
        Vehicle::factory(3)->create([
            'is_published' => true,
            'dealership_id' => $dealership1->id,
        ]);
        Vehicle::factory(2)->create([
            'is_published' => true,
            'dealership_id' => $dealership2->id,
        ]);

        // Offer targets dealership1, dealership2 (stored as JSON array)
        $offer = CatalogOffer::factory()->create(['active' => true]);
        CatalogOfferRule::query()->create([
            'catalog_offer_id' => $offer->id,
            'field' => 'dealership_id',
            'condition' => 'in',
            'value' => json_encode([$dealership1->id, $dealership2->id]),
        ]);

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

        $response->assertStatus(200);
        // 5 vehicles + 1 catalog offer
        $this->assertEquals(5, $response->json('results.total'));
        $this->assertCount(6, $response->json('results.data'));

        $catalogOfferInResults = collect($response->json('results.data'))
            ->firstWhere('type', 'catalog-offer');
        $this->assertNotNull($catalogOfferInResults);
    }

    public function testCatalogOfferWithInConditionExcludesNonMatchingDealerships()
    {
        Queue::fake();
        $dealership1 = Dealership::factory()->create();
        $dealership2 = Dealership::factory()->create();
        $dealership3 = Dealership::factory()->create();

        // All vehicles at dealership3 (not in offer's target list)
        Vehicle::factory(5)->create([
            'is_published' => true,
            'dealership_id' => $dealership3->id,
        ]);

        // Offer targets dealership1, dealership2 only
        $offer = CatalogOffer::factory()->create(['active' => true]);
        CatalogOfferRule::query()->create([
            'catalog_offer_id' => $offer->id,
            'field' => 'dealership_id',
            'condition' => 'in',
            'value' => json_encode([$dealership1->id, $dealership2->id]),
        ]);

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

        $response->assertStatus(200);
        // Only 5 vehicles, no catalog offer since no vehicles match the rule
        $this->assertEquals(5, $response->json('results.total'));

        $catalogOfferInResults = collect($response->json('results.data'))
            ->firstWhere('type', 'catalog-offer');
        $this->assertNull($catalogOfferInResults);
    }

    public function testCatalogOfferWithNotInConditionExcludesDealerships()
    {
        Queue::fake();
        $dealership1 = Dealership::factory()->create();
        $dealership2 = Dealership::factory()->create();
        $dealership3 = Dealership::factory()->create();

        // Vehicles at dealership3 (not excluded)
        Vehicle::factory(3)->create([
            'is_published' => true,
            'dealership_id' => $dealership3->id,
        ]);

        // Vehicles at dealership1 (excluded)
        Vehicle::factory(2)->create([
            'is_published' => true,
            'dealership_id' => $dealership1->id,
        ]);

        // Offer excludes dealership1 and dealership2
        $offer = CatalogOffer::factory()->create(['active' => true]);
        CatalogOfferRule::query()->create([
            'catalog_offer_id' => $offer->id,
            'field' => 'dealership_id',
            'condition' => 'not_in',
            'value' => json_encode([$dealership1->id, $dealership2->id]),
        ]);

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

        $response->assertStatus(200);
        // 5 vehicles total, catalog offer appears because dealership3 vehicles exist
        $this->assertEquals(5, $response->json('results.total'));
        $this->assertCount(6, $response->json('results.data'));

        $catalogOfferInResults = collect($response->json('results.data'))
            ->firstWhere('type', 'catalog-offer');
        $this->assertNotNull($catalogOfferInResults);
    }

    public function testCatalogOfferWithInConditionAndMultipleMakes()
    {
        Queue::fake();
        $make1 = VehicleMake::factory()->create();
        $make2 = VehicleMake::factory()->create();
        $make3 = VehicleMake::factory()->create();

        // Vehicles with make1 and make2
        Vehicle::factory(2)->create([
            'is_published' => true,
            'make_id' => $make1->id,
        ]);
        Vehicle::factory(2)->create([
            'is_published' => true,
            'make_id' => $make2->id,
        ]);
        // Vehicle with make3 (not in offer target)
        Vehicle::factory(1)->create([
            'is_published' => true,
            'make_id' => $make3->id,
        ]);

        // Offer targets make1 and make2
        $offer = CatalogOffer::factory()->create(['active' => true]);
        CatalogOfferRule::query()->create([
            'catalog_offer_id' => $offer->id,
            'field' => 'make_id',
            'condition' => 'in',
            'value' => json_encode([$make1->id, $make2->id]),
        ]);

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

        $response->assertStatus(200);
        // 5 vehicles + 1 catalog offer (because some vehicles match the rule)
        $this->assertEquals(5, $response->json('results.total'));
        $this->assertCount(6, $response->json('results.data'));

        $catalogOfferInResults = collect($response->json('results.data'))
            ->firstWhere('type', 'catalog-offer');
        $this->assertNotNull($catalogOfferInResults);
    }
}
