<?php

namespace Tests\Unit\Services;

use App\Services\DealershipDetection;
use App\Services\LocatingService;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Support\Facades\App;
use Mtc\MercuryDataModels\Dealership;
use Mockery;
use Tests\TestCase;
use Mtc\MercuryDataModels\Services\Location;

class DealershipDetectionTest extends TestCase
{
    use RefreshDatabase;

    protected $tenancy = true;

    public function testCanReturnDealerByDealerId()
    {
        $dealer = Dealership::factory()->create();

        $result = (new DealershipDetection)->determineDealer(
            fallbackDealerId: null,
            dealer_id: $dealer->id,
            postcode: null
        );

        $this->assertEquals($dealer->id, $result->id);
    }

    public function testCanReturnDealerByPostcode()
    {
        Dealership::factory()->create([
            'id' => 1,
            'coordinates' => '51.5014,-0.1419'
        ]);

        $locatingService = Mockery::mock(LocatingService::class);
        $locatingService->shouldReceive('locate')->with('SW1A 1AA')->andReturnSelf();
        $locatingService->shouldReceive('lat')->andReturn(51.5007);
        $locatingService->shouldReceive('lng')->andReturn(-0.1246);
        $locatingService->shouldReceive('getDistanceBetween')->andReturn(1);

        $this->app->instance(LocatingService::class, $locatingService);

        $service = new DealershipDetection();

        $dealer = $service->determineDealer(null, null, 'SW1A 1AA');

        $this->assertNotNull($dealer);
        $this->assertEquals(1, $dealer->id);
    }

    public function testItUsesFallbackDealerID()
    {
        $fallback = Dealership::factory()->create();

        $result = (new DealershipDetection)->determineDealer(
            fallbackDealerId: $fallback->id,
            dealer_id: null,
            postcode: null
        );

        $this->assertEquals($fallback->id, $result->id);
    }

    public function testReturnsNullIfNoValidDealerID()
    {
        $result = (new DealershipDetection)->determineDealer(
            fallbackDealerId: null,
            dealer_id: null,
            postcode: null
        );

        $this->assertNull($result);
    }

    public function testReturnsNullIfPostcodeLookupFails()
    {
        $locatingService = Mockery::mock(LocatingService::class);
        $locatingService->shouldReceive('locate')
            ->with('UNKNOWN')
            ->andThrow(new \Exception('Invalid postcode'));

        App::instance(LocatingService::class, $locatingService);

        $detection = new DealershipDetection();
        $result = $detection->getClosestDealershipToPostcode('UNKNOWN');

        $this->assertNull($result);
    }

    public function testReturnsNullIfCoordinatesAreInvalid()
    {
        Dealership::factory()->create([
            'id' => 1,
            'coordinates' => '51.5014,-0.1419',
        ]);

        $locatingService = Mockery::mock(LocatingService::class);
        $locatingService->shouldReceive('locate')->with('INVALID')->andReturnSelf();
        $locatingService->shouldReceive('lat')->andReturn(null);
        $locatingService->shouldReceive('lng')->andReturn(null);

        $this->app->instance(LocatingService::class, $locatingService);

        $service = new DealershipDetection();

        $dealer = $service->determineDealer(null, null, 'INVALID');

        $this->assertNull($dealer);
    }

    public function testReturnsNullINoDealersHaveCoordinates()
    {
        Dealership::factory()->create(['coordinates' => null]);

        $locatingService = Mockery::mock(LocatingService::class);
        $locatingService->shouldReceive('locate')->andReturnSelf();
        $locatingService->shouldReceive('lat')
            ->andReturn(10.0);
        $locatingService->shouldReceive('lng')
            ->andReturn(10.0);

        $this->app->instance(LocatingService::class, $locatingService);

        $detection = new DealershipDetection();
        $result = $detection->getClosestDealershipToPostcode('XYZ');

        $this->assertNull($result);
    }

    protected function tearDown(): void
    {
        parent::tearDown();
        Mockery::close();
    }
}
