<?php

namespace Tests\Feature\Controllers;

use App\Facades\Settings;
use App\Jobs\BulkImportJob;
use Database\Seeders\MercuryTheme\ContentElementSeeder;
use Database\Seeders\MercuryTheme\TemplateSeeder;
use Illuminate\Http\UploadedFile;
use Illuminate\Support\Facades\Bus;
use Illuminate\Support\Facades\File;
use Illuminate\Support\Facades\Queue;
use Mtc\MercuryDataModels\Template;
use Mtc\MercuryDataModels\VehicleOffer;
use Mtc\MercuryDataModels\VehicleOfferContentHistory;
use Tests\DatabaseTestCase;
use Tests\UserForTenant;

class VehicleOfferControllerTest extends DatabaseTestCase
{
    use UserForTenant;

    /**
     * A basic feature test example.
     *
     * @return void
     */
    public function testIndex()
    {
        VehicleOffer::factory(5)->create();
        $response = $this->actingAs($this->getUser())
            ->withHeader('X-Tenant', tenant('id'))
            ->getJson(route('tenant.vehicle-offers.index'));

        $response->assertStatus(200);
        $this->assertIsArray($response->json('data'));
        $this->assertCount(5, $response->json('data'));
    }

    public function testStore()
    {
        $response = $this->actingAs($this->getUser())
            ->withHeader('X-Tenant', tenant('id'))
            ->postJson(route('tenant.vehicle-offers.store'), [
                'name' => 'foo',
            ]);

        $response->assertStatus(201);
        $this->assertEquals('foo', $response->json('offer.name'));
        $this->assertTrue(VehicleOffer::query()->where('name', 'foo')->exists());
    }

    public function testShow()
    {
        $offer = VehicleOffer::factory()->create([
            'name' => 'foo',
            'type_id' => 11,
        ]);

        $response = $this->actingAs($this->getUser())
            ->withHeader('X-Tenant', tenant('id'))
            ->getJson(route('tenant.vehicle-offers.show', $offer));

        $response->assertStatus(200);
        $this->assertEquals('foo', $response->json('offer.name'));
        $this->assertEquals(11, $response->json('offer.type_id'));
    }

    public function testUpdate()
    {
        $offer = VehicleOffer::factory()->create([
            'name' => 'foo',
            'type_id' => 11,
        ]);

        $response = $this->actingAs($this->getUser())
            ->withHeader('X-Tenant', tenant('id'))
            ->putJson(route('tenant.vehicle-offers.update', $offer), [
                'name' => 'baz',
                'featured' => 0,
                'published' => 1,
                'derivative' => 'foo baz bar',
                'make_id' => 8,
                'model_id' => 9,
                'type_id' => 10,
                'form_id' => 11,
                'seo' => [],
                'data' => [],
                'content' => [],
            ]);

        $offer->refresh();
        $response->assertStatus(200);
        $this->assertEquals('baz', $response->json('offer.name'));
        $this->assertEquals(10, $response->json('offer.type_id'));
        $this->assertEquals('baz', $offer->name);
        $this->assertEquals('foo baz bar', $offer->derivative);
        $this->assertEquals(8, $offer->make_id);
        $this->assertEquals(9, $offer->model_id);
        $this->assertEquals(10, $offer->type_id);
        $this->assertEquals(11, $offer->form_id);
        $this->assertEquals(1, $offer->published);
    }

    public function testUpdateRejectsDuplicateEquipmentDescriptions(): void
    {
        $offer = VehicleOffer::factory()->create([
            'name' => 'foo',
            'type_id' => 11,
        ]);

        $response = $this->actingAs($this->getUser())
            ->withHeader('X-Tenant', tenant('id'))
            ->putJson(route('tenant.vehicle-offers.update', $offer), [
                'name' => 'baz',
                'featured' => 0,
                'published' => 1,
                'equipment' => [
                    ['id' => null, 'description' => 'High-End Audio System'],
                    ['id' => null, 'description' => 'High-End Audio System'],
                ],
            ]);

        $response->assertStatus(422);
        $response->assertJsonValidationErrors('equipment');
        $this->assertDatabaseMissing('vehicle_standard_equipment', [
            'vehicle_id' => $offer->id,
            'description' => 'High-End Audio System',
        ]);
    }

    public function testDestroy()
    {
        $offer = VehicleOffer::factory()->create();

        $response = $this->actingAs($this->getUser())
            ->withHeader('X-Tenant', tenant('id'))
            ->deleteJson(route('tenant.vehicle-offers.destroy', $offer));

        $response->assertStatus(200);
        $this->assertFalse(VehicleOffer::query()->where('id', $offer->id)->exists());
    }

    public function testGetFeatured()
    {
        VehicleOffer::factory(3)->create(['featured' => 1]);
        VehicleOffer::factory(5)->create(['featured' => 0]);

        $response = $this->actingAs($this->getUser())
            ->withHeader('X-Tenant', tenant('id'))
            ->getJson(route('tenant.vehicle-offers.get-featured'));
        $response->assertStatus(200);

        $this->assertIsArray($response->json('data'));
        $this->assertCount(3, $response->json('data'));
    }

    public function testSetFeatured()
    {
        $to_set_featured = VehicleOffer::factory(3)->create(['featured' => 0]);
        VehicleOffer::factory(5)->create(['featured' => 0]);

        $response = $this->actingAs($this->getUser())
            ->withHeader('X-Tenant', tenant('id'))
            ->putJson(route('tenant.vehicle-offers.set-featured'), [
                'offer_ids' => $to_set_featured->pluck('id')
            ]);

        $response->assertStatus(200);

        $this->assertIsArray($response->json('data'));
        $this->assertCount(3, $response->json('data'));
        $this->assertEquals(3, VehicleOffer::query()->where('featured', 1)->count());

        $featured_ids = collect($response->json('data'))->pluck('id');
        $this->assertEquals($to_set_featured->pluck('id')->toArray(), $featured_ids->toArray());
    }

    public function testExport()
    {
        VehicleOffer::factory(3)->create(['featured' => 1]);
        VehicleOffer::factory(5)->create(['featured' => 0]);

        $response = $this->actingAs($this->getUser())
            ->withHeader('X-Tenant', tenant('id'))
            ->postJson(route('tenant.vehicle-offers.export'));
        $response->assertStatus(200);
        $response->assertDownload();
    }

    public function testTemplateSync()
    {
        $template = Template::factory()->create();
        $offer = VehicleOffer::factory()->create(['template_id' => $template->id]);

        $response = $this->actingAs($this->getUser())
            ->withHeader('X-Tenant', tenant('id'))
            ->postJson(route('tenant.vehicle-offers.sync-template', $offer));
        $response->assertStatus(200);
    }

    public function testVersionRestore()
    {
        $offer = VehicleOffer::factory()->create();
        $version = VehicleOfferContentHistory::factory()->create(['offer_id' => $offer->id]);


        $response = $this->actingAs($this->getUser())
            ->withHeader('X-Tenant', tenant('id'))
            ->postJson(route('tenant.vehicle-offers.restore-version', [$offer, $version]));
        $response->assertStatus(200);
        $version->refresh();
        $this->assertTrue($version->is_active);
    }

    public function testImport()
    {
        Queue::fake();
        $response = $this->actingAs($this->getUser())
            ->withHeader('X-Tenant', tenant('id'))
            ->postJson(route('tenant.vehicle-offers.import'), [
                'file' => UploadedFile::fake()->create('offers.xlsx'),
            ]);

        $response->assertStatus(200);
        Queue::assertPushed(BulkImportJob::class);
    }

    public function testVehicleImport()
    {
        VehicleOffer::query()->delete();

        Bus::fake();
        $response = $this->actingAs($this->getUser())
            ->withHeader('X-Tenant', tenant('id'))
            ->withoutExceptionHandling()
            ->postJson(route('tenant.vehicle-offers.import'), [
                'file' => UploadedFile::fake()
                    ->createWithContent('offers.xlsx', File::get(dirname(__DIR__, 2) . '/data/offers.xlsx')),
            ]);

        Bus::assertDispatched(BulkImportJob::class);

        $dispatchedJob = Bus::dispatched(BulkImportJob::class)->first();
        $reflection = new \ReflectionClass($dispatchedJob);
        $getJobProperty = function ($name) use ($reflection, $dispatchedJob) {
            $prop = $reflection->getProperty($name);
            return $prop->getValue($dispatchedJob);
        };

        $this->assertEquals('offer', $getJobProperty('type'));
        $this->assertEquals('edit-offers', $getJobProperty('permission'));

        new BulkImportJob('offer', $getJobProperty('pathToFile'), 'edit-offers')->handle();

        $response->assertStatus(200);
        $this->assertEquals(14, VehicleOffer::query()->count());
    }

    public function testContentUpdateAdVersionSaving()
    {
        tenant()->tier = 'pro';
        Settings::make([
            'tab' => 'foo-baz',
            'section' => 'lorem',
            'group' => 'ipsum',
            'name' => 'dolor',
            'value' => true,
            'type' => 'bool',
            'config_key' => 'offers-versioning-enabled',
        ]);
        new ContentElementSeeder()->run();
        new TemplateSeeder()->run();
        $template = Template::query()->where('slug', 'offers-list')->first();
        $offer = VehicleOffer::factory()->create([
            'name' => 'foo',
            'template_id' => $template->id,
        ]);

        $response = $this->actingAs($this->getUser())
            ->withHeader('X-Tenant', tenant('id'))
            ->postJson(route('tenant.vehicle-offers.sync-template', $offer));

        $response = $this->actingAs($this->getUser())
            ->withHeader('X-Tenant', tenant('id'))
            ->getJson(route('tenant.vehicle-offers.show', $offer));

        $newCar = $response->json('offer');

        $newCar['content'][0]['children'][0]['content'] = 'foo';
        $newCar['content'][0]['children'][1]['content'] = 'baz bar';
        $response = $this->actingAs($this->getUser())
            ->withHeader('X-Tenant', tenant('id'))
            ->withoutExceptionHandling()
            ->putJson(route('tenant.vehicle-offers.update', $offer), $newCar);

        $response->assertStatus(200);
        $this->assertEquals('foo', $response->json('offer.content.0.children.0.content'));
        $this->assertEquals('baz bar', $response->json('offer.content.0.children.1.content'));
        $this->assertEquals(1, $offer->versions()->count());
    }

    public function testCopy()
    {
        $offer = VehicleOffer::factory()->create([
            'name' => 'foo',
        ]);

        $response = $this->actingAs($this->getUser())
            ->withHeader('X-Tenant', tenant('id'))
            ->postJson(route('tenant.vehicle-offers.copy', $offer));

        $response->assertStatus(201);
        $this->assertTrue(VehicleOffer::query()->where('id', $response->json('offer.id'))->exists());
        $this->assertNotEquals($offer->id, $response->json('offer.id'));
        $this->assertEquals($offer->name, $response->json('offer.name'));
    }

    public function testWeightSavedWhenFeatureOff(): void
    {
        $response = $this->actingAs($this->getUser())
            ->withHeader('X-Tenant', tenant('id'))
            ->postJson(route('tenant.vehicle-offers.store'), [
                'name' => 'weighted offer',
                'weight' => 5,
            ]);

        $response->assertStatus(201);
        $this->assertEquals(5, VehicleOffer::query()->where('name', 'weighted offer')->first()->weight);
    }

    public function testStoreWeightedOfferWithinCap(): void
    {
        $this->seedMaxWeightedSetting(2);

        VehicleOffer::factory()->create(['weight' => 3]);

        $response = $this->actingAs($this->getUser())
            ->withHeader('X-Tenant', tenant('id'))
            ->postJson(route('tenant.vehicle-offers.store'), [
                'name' => 'new weighted',
                'weight' => 5,
            ]);

        $response->assertStatus(201);
        $this->assertEquals(2, VehicleOffer::query()->where('weight', '>', 0)->count());
    }

    public function testStoreWeightedOfferDemotesLowestWhenCapExceeded(): void
    {
        $this->seedMaxWeightedSetting(2);

        $lowest = VehicleOffer::factory()->create(['weight' => 1]);
        $middle = VehicleOffer::factory()->create(['weight' => 5]);

        $response = $this->actingAs($this->getUser())
            ->withHeader('X-Tenant', tenant('id'))
            ->postJson(route('tenant.vehicle-offers.store'), [
                'name' => 'new top',
                'weight' => 10,
            ]);

        $response->assertStatus(201);
        $this->assertEquals(0, $lowest->fresh()->weight);
        $this->assertEquals(5, $middle->fresh()->weight);
        $this->assertEquals(10, VehicleOffer::query()->where('name', 'new top')->first()->weight);
        $this->assertEquals(2, VehicleOffer::query()->where('weight', '>', 0)->count());
    }

    public function testUpdateWeightedOfferDemotesLowestWhenCapExceeded(): void
    {
        $this->seedMaxWeightedSetting(2);

        $lowest = VehicleOffer::factory()->create(['weight' => 1]);
        $middle = VehicleOffer::factory()->create(['weight' => 5]);
        $target = VehicleOffer::factory()->create(['weight' => 0, 'name' => 'target']);

        $response = $this->actingAs($this->getUser())
            ->withHeader('X-Tenant', tenant('id'))
            ->putJson(route('tenant.vehicle-offers.update', $target), [
                'name' => 'target',
                'featured' => 0,
                'published' => 1,
                'weight' => 10,
                'seo' => [],
                'data' => [],
                'content' => [],
            ]);

        $response->assertStatus(200);
        $this->assertEquals(0, $lowest->fresh()->weight);
        $this->assertEquals(5, $middle->fresh()->weight);
        $this->assertEquals(10, $target->fresh()->weight);
        $this->assertEquals(2, VehicleOffer::query()->where('weight', '>', 0)->count());
    }

    private function seedMaxWeightedSetting(int $value): void
    {
        Settings::make([
            'tab' => 'Automotive',
            'section' => 'SRP',
            'group' => 'Vehicle Offers',
            'name' => 'Max weighted vehicle offers',
            'config_key' => 'vehicle-offer-max-weighted',
            'type' => 'integer',
            'value' => $value,
        ]);
    }
}
