<?php

namespace Tests\Tenant;

use App\Facades\Settings;
use Carbon\Carbon;
use Mtc\ContentManager\Models\MediaSize;
use Mtc\MercuryDataModels\Dealership;
use Mtc\MercuryDataModels\DealershipDepartment;
use Mtc\MercuryDataModels\DealershipHoliday;
use Mtc\MercuryDataModels\Franchise;
use Mtc\MercuryDataModels\Media;
use Mtc\MercuryDataModels\MediaUse;
use Mtc\MercuryDataModels\SeoData;
use Mtc\MercuryDataModels\SeoDefault;
use Mtc\MercuryDataModels\VehicleMake;
use Tests\TenantTestCase;

class DealershipControllerTest extends TenantTestCase
{
    public function testDealershipIndex()
    {
        Dealership::factory(3)->create();
        $response = $this
            ->getJson(route('dealerships.index'));

        $response->assertStatus(200);
        $this->assertArrayHasKey('page', $response->json());
        $this->assertArrayHasKey('dealerships', $response->json());
    }

    public function testDealershipShow()
    {
        $dealership = Dealership::factory()->create();
        $response = $this
            ->getJson(route('dealerships.show', $dealership));

        $response->assertStatus(200);
    }

    public function testMedia()
    {
        MediaSize::factory()->create([
            'model' => 'dealership',
            'label' => 'foo'
        ]);
        MediaSize::factory()->create([
            'model' => 'dealership',
            'label' => 'faz'
        ]);
        MediaSize::factory()->create([
            'model' => 'dealership',
            'label' => 'bar'
        ]);

        $dealership = Dealership::factory()->create();
        $media_item = Media::factory()->create();
        MediaUse::factory()->create([
            'media_id' => $media_item->id,
            'owner_type' => 'dealership',
            'owner_id' => $dealership->id,
            'allowed_sizes' => [],
        ]);

        $response = $this
            ->getJson(route('dealerships.show', $dealership));

        $response->assertStatus(200);

        $this->assertCount(1, $response->json('images'));
        // 4 due to 3 sizes + original
        $this->assertCount(4, $response->json('images.0.sizes'));

        foreach ($response->json('images.0.sizes') as $size_name => $size_path) {
            $this->assertStringContainsString($media_item->src, $size_path);
        }
    }

    public function testDealershipFindUsSeo()
    {
        $defaults = [
            [
                'section' => 'page',
                'title' => '{{TITLE}} | {{SITE_NAME}} (page)',
                'description' => '{{CONTENT_EXCERPT}} (page)',
            ],
            [
                'section' => 'dealership',
                'title' => '{{TITLE}} | {{SITE_NAME}}',
                'description' => '(dealership)',
            ],
            [
                'section' => 'offer',
                'title' => '{{TITLE}} | {{MAKE}} Offers | {{SITE_NAME}} (offer)',
                'description' => '{{CONTENT_EXCERPT}} (offer)',
            ],
            [
                'section' => 'filter',
                'title' => '{{BODY_STYLE}} {{MAKE}} {{MODEL}} Used Vehicle Offers | Page {{PAGE}} | {{SITE_NAME}}',
                'description' => '',
            ],
            [
                'section' => 'offers',
                'title' => '{{BODY_STYLE}} {{MAKE}} {{MODEL}} New Vehicle Offers | {{SITE_NAME}}',
                'description' => '',
            ],
            [
                'section' => 'vehicle',
                'title' => '{{TITLE}} | {{MAKE}} Offers | {{SITE_NAME}} (vehicle)',
                'description' => '{{CONTENT_EXCERPT}} (vehicle)',
            ],
        ];

        collect($defaults)
            ->each(fn ($default) => SeoDefault::query()->updateOrCreate(['section' => $default['section']], $default));

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

        SeoData::factory()->create([
            'path' => '/dealership/' . $dealership->slug,
            'title' => 'seo_title',
            'description' => 'seo_description',
        ]);

        $response = $this
            ->withHeader('x-path', '/my-franchise/find-us/' . $dealership->slug)
            ->getJson(route('dealerships.show', $dealership));

        $response->assertStatus(200);

        // assert that we get the expected SEO data
        $this->assertArrayHasKey('seo', $response->json());
        $this->assertEquals('seo_title', $response->json('seo.title'));
        $this->assertEquals('seo_description', $response->json('seo.description'));
    }

    public function testDealershipListDepartments()
    {
        $dealership = Dealership::factory()->create();

        DealershipDepartment::query()->create([
            'dealership_id' => $dealership->id,
            'name' => 'foo',
            'open_times' => [],
        ]);

        DealershipDepartment::query()->create([
            'dealership_id' => $dealership->id,
            'name' => 'bar',
            'open_times' => [],
        ]);

        $response = $this
            ->getJson(route('dealerships.index'));

        $response->assertStatus(200);
        $this->assertArrayHasKey('dealerships', $response->json());
        $this->assertCount(1, $response->json('dealerships'));
        $this->assertArrayHasKey('departments', $response->json('dealerships.0'));
        $this->assertCount(2, $response->json('dealerships.0.departments'));

        collect($response->json('dealerships.0.departments'))->each(function ($department) {
            $this->assertContains(
                $department['name'],
                [
                    'foo',
                    'bar'
                ],
            );
        });
    }

    public function testDealershipViewDepartments()
    {
        $dealership = Dealership::factory()->create();

        DealershipDepartment::query()->create([
            'dealership_id' => $dealership->id,
            'name' => 'foo',
            'open_times' => [],
        ]);

        DealershipDepartment::query()->create([
            'dealership_id' => $dealership->id,
            'name' => 'bar',
            'open_times' => [],
        ]);

        $response = $this
            ->getJson(route('dealerships.show', $dealership));

        $response->assertStatus(200);

        $this->assertArrayHasKey('departments', $response->json());
        $this->assertCount(2, $response->json('departments'));

        collect($response->json('departments'))->each(function ($department) {
            $this->assertContains(
                $department['name'],
                [
                    'foo',
                    'bar'
                ],
            );
        });
    }

    public function testDealershipsWithoutDepartmentsOpenTimesFromSettings()
    {
        Settings::make([
            'tab' => 'foo',
            'section' => 'lorem',
            'group' => 'ipsum',
            'name' => 'dolor',
            'config_key' => 'dealership-secondary-open-close-time-name',
            'value' => 'baz',
            'type' => 'string',
        ]);

        Settings::make([
            'tab' => 'foo',
            'section' => 'lorem',
            'group' => 'ipsum',
            'name' => 'dolor',
            'config_key' => 'automotive-dealership-open-time',
            'value' => '00:00',
            'type' => 'string',
        ]);

        Settings::make([
            'tab' => 'foo',
            'section' => 'lorem',
            'group' => 'ipsum',
            'name' => 'dolor',
            'config_key' => 'automotive-dealership-close-time',
            'value' => '23:59',
            'type' => 'string',
        ]);

        Settings::make([
            'tab' => 'foo',
            'section' => 'lorem',
            'group' => 'ipsum',
            'name' => 'dolor',
            'config_key' => 'automotive-dealership-open-days',
            'value' => 'all',
            'type' => 'string',
        ]);


        Dealership::factory()->create();

        $response = $this
            ->getJson(route('dealerships.index'));

        $response->assertStatus(200);
        $this->assertArrayHasKey('dealerships', $response->json());
        $this->assertCount(1, $response->json('dealerships'));
        $this->assertCount(0, $response->json('dealerships.0.departments'));

        $this->assertEquals('00:00', $response->json('dealerships.0.open_times.0.open'));
        $this->assertEquals('23:59', $response->json('dealerships.0.open_times.0.close'));
        $this->assertNull($response->json('dealerships.0.alt_open_times.0.open'));
        $this->assertNull($response->json('dealerships.0.alt_open_times.0.close'));
        $this->assertNull($response->json('dealerships.0.second_alt_open_times.0.open'));
        $this->assertNull($response->json('dealerships.0.second_alt_open_times.0.close'));
    }

    public function testDealershipsWithoutDepartmentsOpenTimesFromTableData()
    {
        Settings::make([
            'tab' => 'foo',
            'section' => 'lorem',
            'group' => 'ipsum',
            'name' => 'dolor',
            'config_key' => 'dealership-secondary-open-close-time-name',
            'value' => 'baz',
            'type' => 'string',
        ]);

        Settings::make([
            'tab' => 'foo',
            'section' => 'lorem',
            'group' => 'ipsum',
            'name' => 'dolor',
            'config_key' => 'automotive-dealership-open-time',
            'value' => '00:00',
            'type' => 'string',
        ]);

        Settings::make([
            'tab' => 'foo',
            'section' => 'lorem',
            'group' => 'ipsum',
            'name' => 'dolor',
            'config_key' => 'automotive-dealership-close-time',
            'value' => '23:59',
            'type' => 'string',
        ]);

        Settings::make([
            'tab' => 'foo',
            'section' => 'lorem',
            'group' => 'ipsum',
            'name' => 'dolor',
            'config_key' => 'automotive-dealership-open-days',
            'value' => 'all',
            'type' => 'string',
        ]);


        Dealership::factory()->create([
            'open_times' => $this->getOpenTimes('11:11', '22:22')
        ]);

        $response = $this
            ->getJson(route('dealerships.index'));

        $response->assertStatus(200);
        $this->assertArrayHasKey('dealerships', $response->json());
        $this->assertCount(1, $response->json('dealerships'));
        $this->assertCount(0, $response->json('dealerships.0.departments'));

        $this->assertEquals('11:11', $response->json('dealerships.0.open_times.0.open'));
        $this->assertEquals('22:22', $response->json('dealerships.0.open_times.0.close'));
        $this->assertNull($response->json('dealerships.0.alt_open_times.0.open'));
        $this->assertNull($response->json('dealerships.0.alt_open_times.0.close'));
        $this->assertNull($response->json('dealerships.0.second_alt_open_times.0.open'));
        $this->assertNull($response->json('dealerships.0.second_alt_open_times.0.close'));
    }

    public function testDealershipDepartmentOpenTimes()
    {
        Settings::make([
            'tab' => 'foo',
            'section' => 'lorem',
            'group' => 'ipsum',
            'name' => 'dolor',
            'config_key' => 'dealership-secondary-open-close-time-name',
            'value' => 'baz',
            'type' => 'string',
        ]);

        Settings::make([
            'tab' => 'foo',
            'section' => 'lorem',
            'group' => 'ipsum',
            'name' => 'dolor',
            'config_key' => 'dealership-tertiary-open-close-time-name',
            'value' => 'bar',
            'type' => 'string',
        ]);

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

        DealershipDepartment::query()->create([
            'dealership_id' => $dealership->id,
            'name' => 'foo',
            'open_times' => $this->getOpenTimes(),
            'is_primary' => true,
        ]);

        DealershipDepartment::query()->create([
            'dealership_id' => $dealership->id,
            'name' => 'baz',
            'open_times' => $this->getOpenTimes('05:00', '22:00'),
            'is_primary' => false,
        ]);

        DealershipDepartment::query()->create([
            'dealership_id' => $dealership->id,
            'name' => 'bar',
            'open_times' => $this->getOpenTimes('11:00', '17:00'),
            'is_primary' => false,
        ]);

        $response = $this
            ->getJson(route('dealerships.index'));

        $response->assertStatus(200);
        $this->assertArrayHasKey('dealerships', $response->json());
        $this->assertCount(1, $response->json('dealerships'));
        $this->assertCount(3, $response->json('dealerships.0.departments'));

        // test dealership open times
        $this->assertEquals('00:00', $response->json('dealerships.0.open_times.0.open'));
        $this->assertEquals('23:59', $response->json('dealerships.0.open_times.0.close'));
        $this->assertEquals('05:00', $response->json('dealerships.0.alt_open_times.0.open'));
        $this->assertEquals('22:00', $response->json('dealerships.0.alt_open_times.0.close'));
        $this->assertEquals('11:00', $response->json('dealerships.0.second_alt_open_times.0.open'));
        $this->assertEquals('17:00', $response->json('dealerships.0.second_alt_open_times.0.close'));

        // test dealership departments
        $this->assertEquals('00:00', $response->json('dealerships.0.departments.foo.open_times.0.open'));
        $this->assertEquals('23:59', $response->json('dealerships.0.departments.foo.open_times.0.close'));
        $this->assertEquals('05:00', $response->json('dealerships.0.departments.baz.open_times.0.open'));
        $this->assertEquals('22:00', $response->json('dealerships.0.departments.baz.open_times.0.close'));
        $this->assertEquals('11:00', $response->json('dealerships.0.departments.bar.open_times.0.open'));
        $this->assertEquals('17:00', $response->json('dealerships.0.departments.bar.open_times.0.close'));
    }

    public function testDealershipOpenTimeDays()
    {
        $dealership = Dealership::factory()->create();

        DealershipDepartment::query()->create([
            'dealership_id' => $dealership->id,
            'name' => 'foo',
            'open_times' => [
                [
                    "dayOfWeek" => 1,
                    "open" => '22:00',
                    "close" => '23:00'
                ],
                [
                    "dayOfWeek" => 2,
                    "open" => '09:00',
                    "close" => '18:00'
                ],
            ],
            'is_primary' => true,
        ]);

        // 2025-02-03 - Monday
        $this->travelTo(Carbon::create(2025, 2, 3, 18, 30));

        $response = $this
            ->getJson(route('dealerships.show', $dealership));

        $this->assertEquals(1, $response->json('opens_at.dayOfWeek'));
        $this->assertEquals('22:00', $response->json('opens_at.open'));
        $this->assertEquals('23:00', $response->json('opens_at.close'));

        // 2025-02-03 - Monday
        $this->travelTo(Carbon::create(2025, 2, 3, 23, 30));

        $response = $this
            ->getJson(route('dealerships.show', $dealership));

        $this->assertEquals(2, $response->json('opens_at.dayOfWeek'));
        $this->assertEquals('09:00', $response->json('opens_at.open'));
        $this->assertEquals('18:00', $response->json('opens_at.close'));

        // 2025-02-04 - Monday
        $this->travelTo(Carbon::create(2025, 2, 4, 23, 30));

        $response = $this
            ->getJson(route('dealerships.show', $dealership));

        $this->assertEquals(1, $response->json('opens_at.dayOfWeek'));
        $this->assertEquals('22:00', $response->json('opens_at.open'));
        $this->assertEquals('23:00', $response->json('opens_at.close'));
    }

    private function getOpenTimes(string $open = '00:00', $close = '23:59'): array
    {
        return [
            [
                "dayOfWeek" => 1,
                "open" => $open,
                "close" => $close
            ],
            [
                "dayOfWeek" => 2,
                "open" => $open,
                "close" => $close
            ],
            [
                "dayOfWeek" => 3,
                "open" => $open,
                "close" => $close
            ],
            [
                "dayOfWeek" => 4,
                "open" => $open,
                "close" => $close
            ],
            [
                "dayOfWeek" => 5,
                "open" => $open,
                "close" => $close
            ],
            [
                "dayOfWeek" => 6,
                "open" => $open,
                "close" => $close
            ],
            [
                "dayOfWeek" => 7,
                "open" => $open,
                "close" => $close
            ]
        ];
    }

    public function testDealershipsSingleFranchise()
    {
        $franchise = Franchise::factory()->create();
        Dealership::factory()->create(['franchise_id' => $franchise->id]);
        $response = $this
            ->getJson(route('dealerships.index'));

        $response->assertStatus(200);
        $this->assertArrayHasKey('franchise', $response->json('dealerships.0'));
    }

    public function testDealershipsSingleFranchiseExposesMakeSlug()
    {
        $make = VehicleMake::factory()->create(['name' => 'Ford', 'slug' => 'ford']);
        $franchise = Franchise::factory()->create([
            'make_id' => $make->id,
            'slug' => 'ford-transit-centre',
        ]);
        Dealership::factory()->create(['franchise_id' => $franchise->id]);

        $response = $this->getJson(route('dealerships.index'));

        $response->assertStatus(200);
        $this->assertEquals('ford-transit-centre', $response->json('dealerships.0.franchise.slug'));
        $this->assertEquals('ford', $response->json('dealerships.0.franchise.make_slug'));
        $this->assertEquals($make->id, $response->json('dealerships.0.franchise.make_id'));
    }

    public function testDealershipShowExposesFranchiseMakeSlug()
    {
        $make = VehicleMake::factory()->create(['name' => 'Ford', 'slug' => 'ford']);
        $franchise = Franchise::factory()->create([
            'make_id' => $make->id,
            'slug' => 'ford-transit-centre',
        ]);
        $dealership = Dealership::factory()->create(['franchise_id' => $franchise->id]);

        $response = $this->getJson(route('dealerships.show', $dealership));

        $response->assertStatus(200);
        $this->assertEquals('ford-transit-centre', $response->json('franchise.slug'));
        $this->assertEquals('ford', $response->json('franchise.make_slug'));
        $this->assertEquals($make->id, $response->json('franchise.make_id'));
    }

    public function testDealershipsMultipleFranchisesExposeMakeSlug()
    {
        Settings::make([
            'tab' => 'foo',
            'section' => 'lorem',
            'group' => 'ipsum',
            'name' => 'dolor',
            'config_key' => 'dealerships-multiple-franchises',
            'value' => true,
            'type' => 'bool',
        ]);

        $make = VehicleMake::factory()->create(['name' => 'BYD', 'slug' => 'byd']);
        $franchise = Franchise::factory()->create([
            'make_id' => $make->id,
            'slug' => 'byd-london',
        ]);
        $dealership = Dealership::factory()->create();
        $dealership->franchises()->sync([$franchise->id]);

        $response = $this->getJson(route('dealerships.index'));

        $response->assertStatus(200);
        $this->assertEquals('byd-london', $response->json('dealerships.0.franchises.0.slug'));
        $this->assertEquals('byd', $response->json('dealerships.0.franchises.0.make_slug'));
        $this->assertEquals($make->id, $response->json('dealerships.0.franchises.0.make_id'));
    }

    public function testDealershipsMultipleFranchises()
    {
        Settings::make([
            'tab' => 'foo',
            'section' => 'lorem',
            'group' => 'ipsum',
            'name' => 'dolor',
            'config_key' => 'dealerships-multiple-franchises',
            'value' => true,
            'type' => 'bool',
        ]);

        $franchises = Franchise::factory(3)->create();
        $dealership = Dealership::factory()->create();
        $dealership->franchises()->sync($franchises->pluck('id'));

        $response = $this
            ->getJson(route('dealerships.index'));

        $response->assertStatus(200);
        $this->assertArrayHasKey('page', $response->json());
        $this->assertArrayHasKey('franchises', $response->json('dealerships.0'));
        $this->AssertCount(3, $response->json('dealerships.0.franchises'));
    }

    public function testDealershipsOrderedByNameByDefault()
    {
        // Create dealerships with names that would sort differently alphabetically
        Dealership::factory()->create(['name' => 'Zephyr Motors', 'order' => 1]);
        Dealership::factory()->create(['name' => 'Alpha Auto', 'order' => 2]);
        Dealership::factory()->create(['name' => 'Metro Cars', 'order' => 3]);

        $response = $this->getJson(route('dealerships.index'));

        $response->assertStatus(200);
        $this->assertCount(3, $response->json('dealerships'));

        // Should be ordered alphabetically by name (default)
        $this->assertEquals('Alpha Auto', $response->json('dealerships.0.name'));
        $this->assertEquals('Metro Cars', $response->json('dealerships.1.name'));
        $this->assertEquals('Zephyr Motors', $response->json('dealerships.2.name'));
    }

    public function testDealershipsOrderedByNameWhenSettingIsName()
    {
        Settings::make([
            'tab' => 'foo',
            'section' => 'lorem',
            'group' => 'ipsum',
            'name' => 'dolor',
            'config_key' => 'dealerships-sort-order',
            'value' => 'name',
            'type' => 'string',
        ]);

        // Create dealerships with names that would sort differently alphabetically
        Dealership::factory()->create(['name' => 'Zephyr Motors', 'order' => 1]);
        Dealership::factory()->create(['name' => 'Alpha Auto', 'order' => 2]);
        Dealership::factory()->create(['name' => 'Metro Cars', 'order' => 3]);

        $response = $this->getJson(route('dealerships.index'));

        $response->assertStatus(200);
        $this->assertCount(3, $response->json('dealerships'));

        // Should be ordered alphabetically by name
        $this->assertEquals('Alpha Auto', $response->json('dealerships.0.name'));
        $this->assertEquals('Metro Cars', $response->json('dealerships.1.name'));
        $this->assertEquals('Zephyr Motors', $response->json('dealerships.2.name'));
    }

    public function testDealershipViewHolidays()
    {
        $dealership = Dealership::factory()->create();

        // Create upcoming holidays (should be included)
        DealershipHoliday::query()->create([
            'dealership_id' => $dealership->id,
            'date' => Carbon::now()->addDays(3)->format('Y-m-d'),
            'note' => 'Christmas Day',
        ]);

        DealershipHoliday::query()->create([
            'dealership_id' => $dealership->id,
            'date' => Carbon::now()->addDays(10)->format('Y-m-d'),
            'note' => 'New Year',
        ]);

        // Past holiday - should NOT be included
        DealershipHoliday::query()->create([
            'dealership_id' => $dealership->id,
            'date' => Carbon::now()->subDays(5)->format('Y-m-d'),
            'note' => 'Past Holiday',
        ]);

        $response = $this
            ->getJson(route('dealerships.show', $dealership));

        $response->assertStatus(200);
        $this->assertArrayHasKey('holidays', $response->json());
        $this->assertCount(2, $response->json('holidays'));
        $this->assertContains(Carbon::now()->addDays(3)->format('Y-m-d'), $response->json('holidays'));
        $this->assertContains(Carbon::now()->addDays(10)->format('Y-m-d'), $response->json('holidays'));
        $this->assertNotContains(Carbon::now()->subDays(5)->format('Y-m-d'), $response->json('holidays'));
    }

    public function testDealershipsOrderedByCustomOrderWhenSettingIsOrder()
    {
        Settings::make([
            'tab' => 'foo',
            'section' => 'lorem',
            'group' => 'ipsum',
            'name' => 'dolor',
            'config_key' => 'dealerships-sort-order',
            'value' => 'order',
            'type' => 'string',
        ]);

        // Create dealerships with custom order that differs from alphabetical
        Dealership::factory()->create(['name' => 'Alpha Auto', 'order' => 3]);
        Dealership::factory()->create(['name' => 'Metro Cars', 'order' => 1]);
        Dealership::factory()->create(['name' => 'Zephyr Motors', 'order' => 2]);

        $response = $this->getJson(route('dealerships.index'));

        $response->assertStatus(200);
        $this->assertCount(3, $response->json('dealerships'));

        // Should be ordered by custom order field, not alphabetically
        $this->assertEquals('Metro Cars', $response->json('dealerships.0.name'));
        $this->assertEquals('Zephyr Motors', $response->json('dealerships.1.name'));
        $this->assertEquals('Alpha Auto', $response->json('dealerships.2.name'));
    }
}
