<?php

namespace Tests\Tenant;

use App\Facades\Settings;
use App\VehicleSpec\Services\HPISpecProvider;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\App;
use Illuminate\Support\Facades\Http;
use Mtc\MercuryDataModels\ApiNotification;
use Mtc\MercuryDataModels\Vehicle;
use Tests\TenantTestCase;

class HPITest extends TenantTestCase
{
    private function seedHpiSettings(): void
    {
        Settings::make([
            'tab' => 'foo-baz',
            'section' => 'lorem',
            'group' => 'ipsum',
            'name' => 'hpi-enabled',
            'config_key' => 'vehicle-spec-providers-hpi-enabled',
            'type' => 'bool',
            'value' => true,
        ]);

        Settings::make([
            'tab' => 'foo-baz',
            'section' => 'lorem',
            'group' => 'ipsum',
            'name' => 'hpi-customer',
            'config_key' => 'vehicle-spec-providers-hpi-customer_code',
            'type' => 'string',
            'value' => 'test',
        ]);

        Settings::make([
            'tab' => 'foo-baz',
            'section' => 'lorem',
            'group' => 'ipsum',
            'name' => 'hpi-pass',
            'config_key' => 'vehicle-spec-providers-hpi-password',
            'type' => 'string',
            'value' => 'test',
        ]);

        Settings::make([
            'tab' => 'foo-baz',
            'section' => 'lorem',
            'group' => 'ipsum',
            'name' => 'hpi-init',
            'config_key' => 'vehicle-spec-providers-hpi-initials',
            'type' => 'string',
            'value' => 'mtc',
        ]);

        Settings::make([
            'tab' => 'foo-baz',
            'section' => 'lorem',
            'group' => 'ipsum',
            'name' => 'hpi-primary',
            'config_key' => 'vehicle-spec-providers-hpi-primary_product_code',
            'type' => 'string',
            'value' => 'foo',
        ]);

        Settings::make([
            'tab' => 'foo-baz',
            'section' => 'lorem',
            'group' => 'ipsum',
            'name' => 'hpi-supp',
            'config_key' => 'vehicle-spec-providers-hpi-supplementary_product_code',
            'type' => 'string',
            'value' => 'bar',
        ]);
    }

    public function testStandardEquipment()
    {
        $this->seedHpiSettings();
        $lookupResponse = file_get_contents(base_path('tests/data/HPI/lookupResponseWithOptions.xml'));

        Http::fake([
            'https://wss.hpi.co.uk/TradeSoap/services/SupplementaryEnquiryV1*' => Http::response($lookupResponse),
            'http://pat-wss.hpi.co.uk/TradeSoap/services/SupplementaryEnquiryV1*' => Http::response($lookupResponse),
            "*" => Http::response([], 400),
        ]);

        $vehicle = Vehicle::factory()->create([
            'registration_number' => 'GN69 JNK',
            'vrm_condensed' => 'GN69JNK',
            'odometer_mi' => 58000,
            'vin' => null,
        ]);

        $hpi = App::make(HPISpecProvider::class);
        $result = $hpi->getSpec($vehicle);

        $this->assertNotNull($result->standard_equipment);
        $this->assertCount(8, $result->standard_equipment);

        $flatten = collect($result->standard_equipment)
            ->map(fn ($equipment) => $equipment->description)
            ->all();

        $this->assertContains('12v power outlet: front and rear, front', $flatten);
        $this->assertContains('Mirror Package', $flatten);
    }

    public function testTechnicalData()
    {
        $this->seedHpiSettings();
        $lookupResponse = file_get_contents(base_path('tests/data/HPI/lookupResponseWithOptions.xml'));

        Http::fake([
            'https://wss.hpi.co.uk/TradeSoap/services/SupplementaryEnquiryV1*' => Http::response($lookupResponse),
            'http://pat-wss.hpi.co.uk/TradeSoap/services/SupplementaryEnquiryV1*' => Http::response($lookupResponse),
            "*" => Http::response([], 400),
        ]);

        $vehicle = Vehicle::factory()->create([
            'registration_number' => 'GN69 JNK',
            'vrm_condensed' => 'GN69JNK',
            'odometer_mi' => 58000,
            'vin' => null,
        ]);

        $hpi = App::make(HPISpecProvider::class);
        $result = $hpi->getSpec($vehicle);

        $this->assertTrue($result->technical_data instanceof Collection);
        $this->assertEquals(12, $result->technical_data->count());


        $get = (fn (string $label) => $result->technical_data->firstWhere('description', $label));

        $this->assertSame('BLUE', $get('Colour')->value);
        $this->assertSame('Petrol', $get('Fuel Type')->value);
        $this->assertSame('Hatchback', $get('Body Type')->value);
        $this->assertSame('1496', $get('Engine Size')->value);
        $this->assertSame('5', (string)$get('Doors')->value);
    }

    public function testStandardEquipmentIsEmpty(): void
    {
        $this->seedHpiSettings();
        $lookupResponse = file_get_contents(base_path('tests/data/HPI/lookupResponse.xml'));

        Http::fake([
            'https://wss.hpi.co.uk/TradeSoap/services/SupplementaryEnquiryV1*' => Http::response($lookupResponse),
            'http://pat-wss.hpi.co.uk/TradeSoap/services/SupplementaryEnquiryV1*' => Http::response($lookupResponse),
            "*" => Http::response([], 400),
        ]);

        $vehicle = Vehicle::factory()->create([
            'registration_number' => 'GN69 JNK',
            'vrm_condensed' => 'GN69JNK',
            'odometer_mi' => 58000,
            'vin' => null,
        ]);

        $hpi = App::make(HPISpecProvider::class);
        $result = $hpi->getSpec($vehicle);

        $this->assertTrue($result->standard_equipment instanceof Collection);
        $this->assertEquals(0, $result->standard_equipment->count());
    }

    public function testHandlesHttpError(): void
    {
        $this->seedHpiSettings();

        Http::fake([
            '*' => Http::response('Internal Error', 500),
        ]);

        $result = app(HPISpecProvider::class)->getSpec(Vehicle::factory()->create());
        $this->assertTrue($result->standard_equipment instanceof Collection);
        $this->assertEquals(collect(), $result->standard_equipment);
        $this->assertTrue($result->technical_data instanceof Collection);
        $this->assertEquals(collect(), $result->technical_data);
    }

    public function testHandlesMalformedXml(): void
    {
        $this->seedHpiSettings();
        Http::fake(['*' => Http::response('<xml>oops', 200, ['Content-Type' => 'text/xml'])]);

        $vehicle = Vehicle::factory()->create([
            'registration_number' => 'AB12 CDE',
            'vrm_condensed' => 'AB12CDE',
        ]);

        $res = app(HPISpecProvider::class)->getSpec($vehicle);
        $this->assertEquals(collect(), $res->standard_equipment);
        $this->assertEquals(collect(), $res->technical_data);

        $notification = ApiNotification::query()->where('provider', 'hpi')->first();
        $this->assertNotNull($notification);
        $this->assertFalse($notification->processed);
        $this->assertSame(200, $notification->data['http_status']);
        $this->assertSame('<xml>oops', $notification->data['result']);
        $this->assertSame('Malformed XML in HPI response', $notification->data['error']);
    }

    public function testRequestContainsVrmSameAsResponse(): void
    {
        $this->seedHpiSettings();
        $lookupResponse = file_get_contents(base_path('tests/data/HPI/lookupResponseWithOptions.xml'));

        Http::fake([
            '*' => Http::response($lookupResponse)
        ]);

        $vehicle = Vehicle::factory()->create([
            'registration_number' => 'GN69 JNK',
            'vrm_condensed' => 'GN69JNK',
            'odometer_mi' => 58000,
            'vin' => null,
        ]);
        app(HPISpecProvider::class)->getSpec($vehicle);

        Http::assertSent(function ($req) {
            $body = (string)$req->body();
            return str_contains($body, 'GN69JNK') &&
                str_contains($req->header('SOAPAction')[0] ?? '', 'Supplem');
        });
    }

    public function testApiNotificationLoggedOnSuccess(): void
    {
        $this->seedHpiSettings();
        $lookupResponse = file_get_contents(base_path('tests/data/HPI/lookupResponseWithOptions.xml'));

        Http::fake(['*' => Http::response($lookupResponse)]);

        $vehicle = Vehicle::factory()->create([
            'registration_number' => 'GN69 JNK',
            'vrm_condensed' => 'GN69JNK',
            'odometer_mi' => 58000,
            'vin' => null,
        ]);

        app(HPISpecProvider::class)->getSpec($vehicle);

        $this->assertCount(1, ApiNotification::query()->where('provider', 'hpi')->get());
        $notification = ApiNotification::query()->where('provider', 'hpi')->first();
        $this->assertNotNull($notification);
        $this->assertTrue($notification->processed);
        $this->assertEquals('GN69JNK', $notification->reference);
        $this->assertEquals(Vehicle::class, $notification->data_model);
        $this->assertArrayHasKey('request', $notification->data);
        $this->assertArrayHasKey('result', $notification->data);
        $this->assertArrayHasKey('endpoint', $notification->data);
        $this->assertSame($lookupResponse, $notification->data['result']);
        $this->assertSame(200, $notification->data['http_status']);
        $this->assertNull($notification->data['error']);
        $this->assertGreaterThan(0, $notification->data['equipment_count']);
        $this->assertGreaterThan(0, $notification->data['technical_data_count']);
    }

    public function testApiNotificationReflectsPartialData(): void
    {
        $this->seedHpiSettings();
        $lookupResponse = file_get_contents(base_path('tests/data/HPI/lookupResponse.xml'));

        Http::fake(['*' => Http::response($lookupResponse)]);

        $vehicle = Vehicle::factory()->create([
            'registration_number' => 'GN69 JNK',
            'vrm_condensed' => 'GN69JNK',
        ]);

        app(HPISpecProvider::class)->getSpec($vehicle);

        $notification = ApiNotification::query()->where('provider', 'hpi')->first();
        $this->assertNotNull($notification);
        $this->assertSame(200, $notification->data['http_status']);
        $this->assertNull($notification->data['error']);
        $this->assertSame(0, $notification->data['equipment_count']);
        $this->assertGreaterThan(0, $notification->data['technical_data_count']);
        $this->assertTrue($notification->processed);
    }

    public function testApiNotificationLoggedOnSoapFault(): void
    {
        $this->seedHpiSettings();
        $faultResponse = file_get_contents(base_path('tests/data/HPI/lookupResponseFault.xml'));

        Http::fake(['*' => Http::response($faultResponse)]);

        $vehicle = Vehicle::factory()->create([
            'registration_number' => 'YX65 XNEE',
            'vrm_condensed' => 'YX65XNEE',
        ]);

        $result = app(HPISpecProvider::class)->getSpec($vehicle);

        $this->assertEquals(0, $result->standard_equipment->count());
        $this->assertEquals(0, $result->technical_data->count());

        $notification = ApiNotification::query()->where('provider', 'hpi')->first();
        $this->assertNotNull($notification);
        $this->assertFalse($notification->processed);
        $this->assertEquals('YX65XNEE', $notification->reference);
        $this->assertSame($faultResponse, $notification->data['result']);
        $this->assertSame(200, $notification->data['http_status']);
        $this->assertNotNull($notification->data['error']);
        $this->assertStringContainsString('VRM is invalid', $notification->data['error']);
        $this->assertStringContainsString('012', $notification->data['error']);
    }

    public function testApiNotificationLoggedOnHttpError(): void
    {
        $this->seedHpiSettings();

        Http::fake(['*' => Http::response('Internal Error', 500)]);

        $vehicle = Vehicle::factory()->create([
            'registration_number' => 'AB12 CDE',
            'vrm_condensed' => 'AB12CDE',
        ]);

        app(HPISpecProvider::class)->getSpec($vehicle);

        $notification = ApiNotification::query()->where('provider', 'hpi')->first();
        $this->assertNotNull($notification);
        $this->assertFalse($notification->processed);
        $this->assertEquals('AB12CDE', $notification->reference);
        $this->assertSame('Internal Error', $notification->data['result']);
        $this->assertSame(500, $notification->data['http_status']);
        $this->assertNotNull($notification->data['error']);
        $this->assertStringContainsString('500', $notification->data['error']);
    }

    public function testApiNotificationLoggedOnConnectionException(): void
    {
        $this->seedHpiSettings();

        Http::fake(function () {
            throw new \Illuminate\Http\Client\ConnectionException('cURL error 28: Connection timed out');
        });

        $vehicle = Vehicle::factory()->create([
            'registration_number' => 'AB12 CDE',
            'vrm_condensed' => 'AB12CDE',
        ]);

        app(HPISpecProvider::class)->getSpec($vehicle);

        $notification = ApiNotification::query()->where('provider', 'hpi')->first();
        $this->assertNotNull($notification);
        $this->assertFalse($notification->processed);
        $this->assertNull($notification->data['result']);
        $this->assertNull($notification->data['http_status']);
        $this->assertNotNull($notification->data['error']);
        $this->assertStringContainsString('Connection timed out', $notification->data['error']);
    }

    public function testApiNotificationRedactsPassword(): void
    {
        $this->seedHpiSettings();
        $lookupResponse = file_get_contents(base_path('tests/data/HPI/lookupResponseWithOptions.xml'));

        Http::fake(['*' => Http::response($lookupResponse)]);

        $vehicle = Vehicle::factory()->create([
            'registration_number' => 'GN69 JNK',
            'vrm_condensed' => 'GN69JNK',
        ]);

        app(HPISpecProvider::class)->getSpec($vehicle);

        $notification = ApiNotification::query()->where('provider', 'hpi')->first();
        $this->assertSame('[ELIDED]', $notification->data['request']['subscriber_details']['password']);
        $this->assertSame('test', $notification->data['request']['subscriber_details']['customer_code']);
    }
}
