<?php

namespace Tests\Feature;

use Carbon\Carbon;
use Database\Seeders\Global\CountrySeeder;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Foundation\Testing\WithFaker;
use Illuminate\Support\Facades\Config;
use Illuminate\Support\Facades\Schema;
use Mtc\MercuryDataModels\Dealership;
use Mtc\MercuryDataModels\DealershipDepartment;
use Mtc\MercuryDataModels\DealershipHoliday;
use Mtc\MercuryDataModels\PostcodeGeo;
use Tests\TestCase;
use Tests\UserForTenant;

class DealershipControllerTest extends TestCase
{
    use RefreshDatabase;
    use UserForTenant;

    protected $tenancy = true;

    /**
     * A basic feature test example.
     *
     * @return void
     */
    public function testDealershipIndex()
    {
        Dealership::factory(3)->create();

        $response = $this->actingAs($this->getUser())
            ->withHeader('X-Tenant', tenant('id'))
            ->getJson(route('tenant.dealerships.index'));

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

    public function testDealershipStore()
    {
        $response = $this->actingAs($this->getUser())
            ->withHeader('X-Tenant', tenant('id'))
            ->postJson(route('tenant.dealerships.store'), [
                'name' => 'foo',
                'departments' => [
                    [
                        'id' => null,
                        'name' => 'updated_dept_name',
                        'is_primary' => true,
                        'email' => 'test@test.com'
                    ]
                ],
            ]);

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

    }

    public function testDealershipUpdate()
    {
        $this->seed(CountrySeeder::class);
        $dealership = Dealership::factory()->create(['name' => 'foo']);

        $response = $this->actingAs($this->getUser())
            ->withHeader('X-Tenant', tenant('id'))
            ->putJson(route('tenant.dealerships.update', $dealership->id), [
                'name' => 'baz',
                'active' => true,
                'email' => 'john@example.com',
                'contact_no' => '123123',
                'address1' => 'foo',
                'address2' => 'baz',
                'city' => 'bar',
                'county' => 'foo-baz',
                'postcode' => '',
                'country' => 'LV',
            ]);

        $dealership->refresh();
        $response->assertStatus(200);
        $this->assertEquals('john@example.com', $response->json('dealership.email'));
        $this->assertEquals('john@example.com', $dealership->email);
        $this->assertEquals('baz', $dealership->name);
        $this->assertEquals('123123', $dealership->contact_no);
        $this->assertEquals('LV', $dealership->country);
    }

    public function testDealershipUpdateDepartments()
    {
        $this->seed(CountrySeeder::class);
        $dealership = Dealership::factory()->create(['name' => 'foo']);

        $response = $this->actingAs($this->getUser())
            ->withHeader('X-Tenant', tenant('id'))
            ->putJson(route('tenant.dealerships.update', $dealership->id), [
                'name' => 'baz',
                'active' => true,
                'email' => 'john@example.com',
                'contact_no' => '123123',
                'address1' => 'foo',
                'address2' => 'baz',
                'city' => 'bar',
                'county' => 'foo-baz',
                'postcode' => '',
                'country' => 'LV',
                'departments' => [
                    [
                        'id' => null,
                        'name' => 'dept_name',
                    ]
                ],
            ]);

        $dealership->refresh();

        // assert that request fails when department lacks is_primary field
        $response->assertStatus(422);

        $response = $this->actingAs($this->getUser())
            ->withHeader('X-Tenant', tenant('id'))
            ->putJson(route('tenant.dealerships.update', $dealership->id), [
                'name' => 'baz',
                'active' => true,
                'email' => 'john@example.com',
                'contact_no' => '123123',
                'address1' => 'foo',
                'address2' => 'baz',
                'city' => 'bar',
                'county' => 'foo-baz',
                'postcode' => '',
                'country' => 'LV',
                'departments' => [
                    [
                        'id' => null,
                        'name' => 'dept_name',
                        'is_primary' => true,
                    ]
                ],
            ]);

        $dealership->refresh();

        $response->assertStatus(200);

        // assert that the department was created
        $this->assertCount(1, DealershipDepartment::query()->get());

        $department = DealershipDepartment::query()->first();

        $response = $this->actingAs($this->getUser())
            ->withHeader('X-Tenant', tenant('id'))
            ->putJson(route('tenant.dealerships.update', $dealership->id), [
                'name' => 'baz',
                'active' => true,
                'email' => 'john@example.com',
                'contact_no' => '123123',
                'address1' => 'foo',
                'address2' => 'baz',
                'city' => 'bar',
                'county' => 'foo-baz',
                'postcode' => '',
                'country' => 'LV',
                'departments' => [
                    [
                        'id' => $department->id,
                        'name' => 'updated_dept_name',
                        'is_primary' => true,
                        'email' => 'test@test.com'
                    ]
                ],
            ]);

        $dealership->refresh();

        $response->assertStatus(200);

        // assert that the existing department was overwritten
        $this->assertCount(1, DealershipDepartment::query()->get());
        $this->assertFalse(DealershipDepartment::query()->where('name', '=', 'dept_name')->exists());
        $this->assertTrue(DealershipDepartment::query()->where('name', '=', 'updated_dept_name')->exists());
        $this->assertTrue(DealershipDepartment::query()->where('email', '=', 'test@test.com')->exists());
    }

    public function testDealershipUpdateMultipleDepartments()
    {
        $this->seed(CountrySeeder::class);
        $dealership = Dealership::factory()->create(['name' => 'foo']);

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

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

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

        $this->assertCount(3, DealershipDepartment::all());

        $response = $this->actingAs($this->getUser())
            ->withHeader('X-Tenant', tenant('id'))
            ->putJson(route('tenant.dealerships.update', $dealership->id), [
                'name' => 'baz',
                'active' => true,
                'email' => 'john@example.com',
                'contact_no' => '123123',
                'address1' => 'foo',
                'address2' => 'baz',
                'city' => 'bar',
                'county' => 'foo-baz',
                'postcode' => '',
                'country' => 'LV',
                'departments' => [
                    [
                        'id' => $department_1->id,
                        'name' => 'dept_name',
                        'is_primary' => true,
                    ],
                    [
                        'id' => $department_2->name,
                        'name' => 'second_dept_name',
                        'is_primary' => false,
                    ]
                ],
            ]);

        $dealership->refresh();

        $response->assertStatus(200);

        // assert that the expected departments were updated or deleted
        $this->assertCount(2, DealershipDepartment::all());
        $this->assertTrue(DealershipDepartment::query()->where('name', '=', 'dept_name')->exists());
        $this->assertTrue(DealershipDepartment::query()->where('name', '=', 'second_dept_name')->exists());
        $this->assertFalse(DealershipDepartment::query()->where('name', '=', 'third_dept_name')->exists());
    }

    public function testDealershipShow()
    {
        $dealership = Dealership::factory()->create(['name' => 'foo']);

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

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

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

        $response->assertStatus(200);
        $this->assertEquals($dealership->id, $response->json('dealership.id'));
        $this->assertEquals('foo', $response->json('dealership.name'));

        $this->assertArrayHasKey('open_times', $response->json('dealership'));
        $this->assertEquals('00:00', $response->json('dealership.open_times.0.open'));
        $this->assertEquals('23:59', $response->json('dealership.open_times.0.close'));

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

    public function testDealershipDelete()
    {
        $dealership = Dealership::factory()->create(['name' => 'foo']);

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

        DealershipHoliday::query()->create([
            'dealership_id' => $dealership->id,
            'date' => Carbon::now(),
        ]);

        $this->assertCount(1, Dealership::query()->where('id', '=', $dealership->id)->get());
        $this->assertCount(1, DealershipDepartment::query()->where('dealership_id', '=', $dealership->id)->get());
        $this->assertCount(1, DealershipHoliday::query()->where('dealership_id', '=', $dealership->id)->get());

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

        $response->assertStatus(200);
        $this->assertFalse(Dealership::query()->where('id', $dealership->id)->exists());
        $this->assertFalse(DealershipDepartment::query()->where('dealership_id', $dealership->id)->exists());
        $this->assertFalse(DealershipHoliday::query()->where('dealership_id', $dealership->id)->exists());
    }

    public function testCopy()
    {
        $dealership = Dealership::factory()->create(['name' => 'foo']);

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

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

    public function testPostcodeLookup()
    {
        $this->buildPaf();

        $this->seed(CountrySeeder::class);
        $dealership = Dealership::factory()->create(['name' => 'foo']);

        $response = $this->actingAs($this->getUser())
            ->withHeader('X-Tenant', tenant('id'))
            ->putJson(route('tenant.dealerships.update', $dealership->id), [
                'name' => 'baz',
                'active' => true,
                'email' => 'john@example.com',
                'contact_no' => '123123',
                'address1' => 'foo',
                'address2' => 'baz',
                'city' => 'bar',
                'county' => 'foo-baz',
                'postcode' => 'DD13JA',
                'country' => 'GB',
            ]);

        $dealership->refresh();
        $response->assertStatus(200);
        $this->assertEquals('GB', $dealership->country);
        $this->assertEquals('11.22,-2.3', $dealership->coordinates);

        $this->tearDownPaf();
    }

    private function buildPaf()
    {
        Schema::create('postcode_geo', function (Blueprint $table) {
            $table->string('postcode', 10)->unique();
            $table->decimal('latitude', 10, 6);
            $table->decimal('longitude', 10, 6);
            $table->timestamps();
        });

        $pafConnectionData = Config::get('database.connections.' . Config::get('database.default'));
        Config::set('database.connections.paf', $pafConnectionData);

        PostcodeGeo::unguard();
        PostcodeGeo::query()->create([
            'postcode' => 'DD13JA',
            'latitude' => 11.22,
            'longitude' => -2.3,
        ]);

        PostcodeGeo::reguard();
    }

    private function tearDownPaf()
    {
        Schema::drop('postcode_geo');
    }

    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
            ]
        ];
    }
}
