<?php

namespace Tests\Unit;

use App\Models\NotificationMap;
use App\NotificationMaps\NotificationPayloadMapper;
use Illuminate\Validation\ValidationException;
use Tests\TestCase;

class NotificationPayloadMapperTest extends TestCase
{
    private NotificationPayloadMapper $mapper;

    protected function setUp(): void
    {
        parent::setUp();
        $this->mapper = new NotificationPayloadMapper();
    }

    private function makeMap(array $fieldMap, array $validation = [], array $config = []): NotificationMap
    {
        $map = new NotificationMap();
        $map->field_map = $fieldMap;
        $map->validation = $validation;
        $map->config = $config;

        return $map;
    }

    public function test_it_resolves_dot_notation_source_paths(): void
    {
        $map = $this->makeMap([
            ['source' => 'vehicle.registration', 'destination' => 'registration_number', 'type' => 'string'],
        ]);

        $result = $this->mapper->map($map, ['vehicle' => ['registration' => 'AB12 CDE']]);

        $this->assertSame('AB12 CDE', $result->attributes['registration_number']);
    }

    public function test_it_coerces_value_types(): void
    {
        $map = $this->makeMap([
            ['source' => 'mileage', 'destination' => 'odometer_mi', 'type' => 'integer'],
            ['source' => 'price', 'destination' => 'price', 'type' => 'float'],
            ['source' => 'is_new', 'destination' => 'is_new', 'type' => 'boolean'],
            ['source' => 'reg_date', 'destination' => 'first_registration_date', 'type' => 'date'],
            ['source' => 'title', 'destination' => 'title', 'type' => 'string'],
        ]);

        $result = $this->mapper->map($map, [
            'mileage' => '12345',
            'price' => '19995.50',
            'is_new' => 'true',
            'reg_date' => '2021-03-01',
            'title' => '  Focus ST  ',
        ]);

        $this->assertSame(12345, $result->attributes['odometer_mi']);
        $this->assertSame(19995.50, $result->attributes['price']);
        $this->assertTrue($result->attributes['is_new']);
        $this->assertSame('2021-03-01 00:00:00', $result->attributes['first_registration_date']);
        $this->assertSame('Focus ST', $result->attributes['title']);
    }

    public function test_non_numeric_value_for_numeric_type_becomes_null(): void
    {
        $map = $this->makeMap([
            ['source' => 'mileage', 'destination' => 'odometer_mi', 'type' => 'integer'],
        ]);

        $result = $this->mapper->map($map, ['mileage' => 'not-a-number']);

        $this->assertNull($result->attributes['odometer_mi']);
    }

    public function test_absent_source_key_leaves_destination_untouched(): void
    {
        $map = $this->makeMap([
            ['source' => 'price', 'destination' => 'price', 'type' => 'float'],
            ['source' => 'colour', 'destination' => 'colour', 'type' => 'string'],
        ]);

        $result = $this->mapper->map($map, ['price' => 9999]);

        $this->assertArrayHasKey('price', $result->attributes);
        $this->assertArrayNotHasKey('colour', $result->attributes);
    }

    public function test_it_routes_attribute_destinations_to_custom_attributes(): void
    {
        $map = $this->makeMap([
            ['source' => 'contact_name', 'destination' => 'attribute.contact_name', 'type' => 'string'],
            ['source' => 'uuid', 'destination' => 'uuid', 'type' => 'string'],
        ]);

        $result = $this->mapper->map($map, ['contact_name' => 'Sales Desk', 'uuid' => 'X1']);

        $this->assertSame('Sales Desk', $result->customAttributes['contact_name']);
        $this->assertArrayNotHasKey('attribute.contact_name', $result->attributes);
        $this->assertSame('X1', $result->attributes['uuid']);
    }

    public function test_image_list_accepts_plain_url_strings(): void
    {
        $map = $this->makeMap([
            ['source' => 'images', 'destination' => 'images', 'type' => 'image_list'],
        ]);

        $result = $this->mapper->map($map, [
            'images' => ['https://cdn/a.jpg', 'https://cdn/b.jpg'],
        ]);

        $this->assertSame(['https://cdn/a.jpg', 'https://cdn/b.jpg'], $result->images);
    }

    public function test_image_list_accepts_objects_with_configured_url_key(): void
    {
        $map = $this->makeMap(
            [['source' => 'media', 'destination' => 'images', 'type' => 'image_list']],
            [],
            ['images' => ['url_key' => 'href']],
        );

        $result = $this->mapper->map($map, [
            'media' => [
                ['href' => 'https://cdn/a.jpg', 'tag' => 'exterior'],
                ['href' => 'https://cdn/b.jpg'],
            ],
        ]);

        $this->assertSame(['https://cdn/a.jpg', 'https://cdn/b.jpg'], $result->images);
    }

    public function test_image_list_trims_dedupes_and_drops_empties(): void
    {
        $map = $this->makeMap([
            ['source' => 'images', 'destination' => 'images', 'type' => 'image_list'],
        ]);

        $result = $this->mapper->map($map, [
            'images' => ['  https://cdn/a.jpg  ', '', 'https://cdn/a.jpg', null, ['url' => 'https://cdn/c.jpg']],
        ]);

        $this->assertSame(['https://cdn/a.jpg', 'https://cdn/c.jpg'], $result->images);
    }

    public function test_required_validation_throws_when_missing(): void
    {
        $map = $this->makeMap(
            [['source' => 'type', 'destination' => 'type', 'type' => 'string']],
            ['uuid' => ['required', 'string'], 'type' => ['required', 'string']],
        );

        $this->expectException(ValidationException::class);

        // Payload has type but no uuid, so the required uuid rule must fail.
        $this->mapper->map($map, ['type' => 'car']);
    }

    public function test_validation_passes_when_required_present(): void
    {
        $map = $this->makeMap(
            [
                ['source' => 'uuid', 'destination' => 'uuid', 'type' => 'string'],
                ['source' => 'type', 'destination' => 'type', 'type' => 'string'],
            ],
            ['uuid' => ['required', 'string'], 'type' => ['required', 'string']],
        );

        $result = $this->mapper->map($map, ['uuid' => 'STK-1', 'type' => 'car']);

        $this->assertSame('STK-1', $result->attributes['uuid']);
        $this->assertSame('car', $result->attributes['type']);
    }

    public function test_required_validation_covers_custom_attributes(): void
    {
        $map = $this->makeMap(
            [['source' => 'postcode', 'destination' => 'attribute.postcode', 'type' => 'string']],
            ['attribute.postcode' => ['required', 'string']],
        );

        $this->expectException(ValidationException::class);

        $this->mapper->map($map, ['something_else' => 'value']);
    }

    public function test_taxonomy_names_are_captured_for_later_resolution(): void
    {
        $map = $this->makeMap([
            ['source' => 'make', 'destination' => 'make_id', 'type' => 'taxonomy', 'taxonomy' => 'make'],
            ['source' => 'model', 'destination' => 'model_id', 'type' => 'taxonomy', 'taxonomy' => 'model'],
        ]);

        $result = $this->mapper->map($map, ['make' => '  Volkswagen  ', 'model' => 'Golf']);

        $this->assertSame(['destination' => 'make_id', 'value' => 'Volkswagen'], $result->taxonomies['make']);
        $this->assertSame(['destination' => 'model_id', 'value' => 'Golf'], $result->taxonomies['model']);
        // Taxonomy names must not leak into the column attribute bag.
        $this->assertArrayNotHasKey('make_id', $result->attributes);
    }

    public function test_dealership_name_is_captured(): void
    {
        $map = $this->makeMap([
            ['source' => 'dealership', 'destination' => 'dealership_id', 'type' => 'dealership'],
        ]);

        $result = $this->mapper->map($map, ['dealership' => 'Leeds Branch']);

        $this->assertSame('Leeds Branch', $result->dealership);
        $this->assertArrayNotHasKey('dealership_id', $result->attributes);
    }

    public function test_equipment_is_whitelisted_and_defaults_type(): void
    {
        $map = $this->makeMap([
            ['source' => 'equipment', 'destination' => 'equipment', 'type' => 'equipment'],
        ]);

        $result = $this->mapper->map($map, [
            'equipment' => [
                ['description' => 'Air conditioning', 'category' => 'Climate', 'ignored' => 'x'],
                ['type' => 'Optional', 'description' => 'Sunroof', 'price' => '950'],
                ['category' => 'no description here'],
            ],
        ]);

        $this->assertSame([
            ['type' => 'Standard', 'description' => 'Air conditioning', 'category' => 'Climate'],
            ['type' => 'Optional', 'description' => 'Sunroof', 'price' => '950'],
        ], $result->equipment);
    }

    public function test_technical_data_is_whitelisted_and_requires_description(): void
    {
        $map = $this->makeMap([
            ['source' => 'technical_data', 'destination' => 'technical_data', 'type' => 'technical_data'],
        ]);

        $result = $this->mapper->map($map, [
            'technical_data' => [
                ['description' => '0-62 mph', 'value' => '8.5s', 'category' => 'Performance', 'nope' => 1],
                ['value' => 'no description'],
            ],
        ]);

        $this->assertSame([
            ['description' => '0-62 mph', 'value' => '8.5s', 'category' => 'Performance'],
        ], $result->technicalData);
    }

    public function test_spec_lists_are_null_when_absent_but_array_when_present(): void
    {
        $map = $this->makeMap([
            ['source' => 'equipment', 'destination' => 'equipment', 'type' => 'equipment'],
            ['source' => 'technical_data', 'destination' => 'technical_data', 'type' => 'technical_data'],
        ]);

        // Neither key present: both stay null so the upsert leaves the relations alone.
        $absent = $this->mapper->map($map, []);
        $this->assertNull($absent->equipment);
        $this->assertNull($absent->technicalData);

        // Present-but-empty: an explicit empty list so the upsert clears the relation.
        $present = $this->mapper->map($map, ['equipment' => []]);
        $this->assertSame([], $present->equipment);
        $this->assertNull($present->technicalData);
    }
}
