<?php

namespace Tests\Unit;

use App\Exports\JsonMapExport;
use App\Models\ExportMap;
use PHPUnit\Framework\TestCase;

class JsonMapExportTest extends TestCase
{
    public function test_produces_yallamotor_shape_with_typed_values_and_nested_photos(): void
    {
        $exportMap = $this->buildExportMap([
            'columns' => ['uuid', 'manufacture_year', 'odometer_km', 'featured', 'images20', 'features'],
            'headers' => ['ad_reference', 'year', 'km_driven', 'featured', 'photos', 'features'],
            'config' => [
                'json_envelope' => '{ "results": "__RESULTS__" }',
                'column_types' => ['string', 'number', 'number', 'boolean', 'array_of_objects', 'array_of_strings'],
                'column_paths' => [null, null, null, null, 'path', null],
            ],
        ]);

        $data = collect([
            [
                'uuid' => 'YM-001',
                'manufacture_year' => 2022,
                'odometer_km' => 45000,
                'featured' => 'Y',
                'images20' => 'https://cdn.example.com/1.jpg, https://cdn.example.com/2.jpg',
                'features' => 'Rear view camera, Cruise control',
            ],
        ]);

        $payload = (new JsonMapExport($data, $exportMap))->toArray();

        $this->assertArrayHasKey('results', $payload);
        $this->assertCount(1, $payload['results']);

        $row = $payload['results'][0];
        $this->assertSame('YM-001', $row['ad_reference']);
        $this->assertSame(2022, $row['year']);
        $this->assertSame(45000, $row['km_driven']);
        $this->assertTrue($row['featured']);
        $this->assertSame(
            [
                ['path' => 'https://cdn.example.com/1.jpg'],
                ['path' => 'https://cdn.example.com/2.jpg'],
            ],
            $row['photos']
        );
        $this->assertSame(['Rear view camera', 'Cruise control'], $row['features']);
    }

    public function test_substitutes_count_and_generated_at_tokens(): void
    {
        $exportMap = $this->buildExportMap([
            'columns' => ['uuid'],
            'headers' => ['ad_reference'],
            'config' => [
                'json_envelope' => '{ "feed_id": "test", "total": "__COUNT__", "generated_at": "__GENERATED_AT__", "results": "__RESULTS__" }',
                'column_types' => ['string'],
            ],
        ]);

        $data = collect([
            ['uuid' => 'A'],
            ['uuid' => 'B'],
            ['uuid' => 'C'],
        ]);

        $payload = (new JsonMapExport($data, $exportMap))->toArray();

        $this->assertSame('test', $payload['feed_id']);
        $this->assertSame(3, $payload['total']);
        $this->assertIsString($payload['generated_at']);
        $this->assertMatchesRegularExpression(
            '/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}/',
            $payload['generated_at']
        );
        $this->assertCount(3, $payload['results']);
    }

    public function test_returns_bare_array_when_no_envelope(): void
    {
        $exportMap = $this->buildExportMap([
            'columns' => ['uuid'],
            'headers' => ['ad_reference'],
            'config' => ['column_types' => ['string']],
        ]);

        $payload = (new JsonMapExport(collect([['uuid' => 'A']]), $exportMap))->toArray();

        $this->assertIsArray($payload);
        $this->assertArrayNotHasKey('results', $payload);
        $this->assertSame([['ad_reference' => 'A']], $payload);
    }

    public function test_boolean_casting_handles_common_truthy_falsy_strings(): void
    {
        $exportMap = $this->buildExportMap([
            'columns' => ['flag'],
            'headers' => ['flag'],
            'config' => ['column_types' => ['boolean']],
        ]);

        $cases = [
            ['Y', true],
            ['N', false],
            ['true', true],
            ['false', false],
            ['1', true],
            ['0', false],
            ['', false],
            [null, false],
        ];

        foreach ($cases as [$input, $expected]) {
            $payload = (new JsonMapExport(collect([['flag' => $input]]), $exportMap))->toArray();
            $this->assertSame($expected, $payload[0]['flag'], 'Failed for input ' . var_export($input, true));
        }
    }

    public function test_number_casting_returns_int_or_float_or_null(): void
    {
        $exportMap = $this->buildExportMap([
            'columns' => ['n'],
            'headers' => ['n'],
            'config' => ['column_types' => ['number']],
        ]);

        $cases = [
            [42, 42],
            ['42', 42],
            ['2.5', 2.5],
            [null, null],
            ['', null],
            ['£1,500.50', 1500.50],
            ['n/a', null],
        ];

        foreach ($cases as [$input, $expected]) {
            $payload = (new JsonMapExport(collect([['n' => $input]]), $exportMap))->toArray();
            $this->assertSame($expected, $payload[0]['n'], 'Failed for input ' . var_export($input, true));
        }
    }

    public function test_array_of_strings_splits_and_trims(): void
    {
        $exportMap = $this->buildExportMap([
            'columns' => ['features'],
            'headers' => ['features'],
            'config' => ['column_types' => ['array_of_strings']],
        ]);

        $payload = (new JsonMapExport(
            collect([['features' => 'A,B ,  C,, D']]),
            $exportMap
        ))->toArray();

        $this->assertSame(['A', 'B', 'C', 'D'], $payload[0]['features']);
    }

    public function test_array_of_objects_with_json_template_path_produces_multi_key_items(): void
    {
        $exportMap = $this->buildExportMap([
            'columns' => ['images20'],
            'headers' => ['media'],
            'config' => [
                'column_types' => ['array_of_objects'],
                'column_paths' => ['{"url": "__VALUE__", "alt": "Vehicle photo"}'],
            ],
        ]);

        $payload = (new JsonMapExport(
            collect([['images20' => 'https://cdn/1.jpg,https://cdn/2.jpg']]),
            $exportMap
        ))->toArray();

        $this->assertSame(
            [
                ['url' => 'https://cdn/1.jpg', 'alt' => 'Vehicle photo'],
                ['url' => 'https://cdn/2.jpg', 'alt' => 'Vehicle photo'],
            ],
            $payload[0]['media']
        );
    }

    public function test_array_of_objects_single_key_string_path_still_works(): void
    {
        // Backwards-compat: the existing "path" string config keeps producing single-key items.
        $exportMap = $this->buildExportMap([
            'columns' => ['images20'],
            'headers' => ['photos'],
            'config' => [
                'column_types' => ['array_of_objects'],
                'column_paths' => ['path'],
            ],
        ]);

        $payload = (new JsonMapExport(
            collect([['images20' => 'https://cdn/1.jpg,https://cdn/2.jpg']]),
            $exportMap
        ))->toArray();

        $this->assertSame(
            [
                ['path' => 'https://cdn/1.jpg'],
                ['path' => 'https://cdn/2.jpg'],
            ],
            $payload[0]['photos']
        );
    }

    public function test_array_of_objects_template_substitutes_value_token_at_any_depth(): void
    {
        $exportMap = $this->buildExportMap([
            'columns' => ['images20'],
            'headers' => ['media'],
            'config' => [
                'column_types' => ['array_of_objects'],
                'column_paths' => ['{"meta": {"primary_url": "__VALUE__", "type": "image"}}'],
            ],
        ]);

        $payload = (new JsonMapExport(
            collect([['images20' => 'a.jpg,b.jpg']]),
            $exportMap
        ))->toArray();

        $this->assertSame(
            [
                ['meta' => ['primary_url' => 'a.jpg', 'type' => 'image']],
                ['meta' => ['primary_url' => 'b.jpg', 'type' => 'image']],
            ],
            $payload[0]['media']
        );
    }

    public function test_array_of_objects_invalid_json_path_falls_back_to_string_key(): void
    {
        // Malformed JSON ({foo without quotes) should not crash — treat as a literal key name.
        $exportMap = $this->buildExportMap([
            'columns' => ['images20'],
            'headers' => ['media'],
            'config' => [
                'column_types' => ['array_of_objects'],
                'column_paths' => ['{not valid json'],
            ],
        ]);

        $payload = (new JsonMapExport(
            collect([['images20' => 'x.jpg']]),
            $exportMap
        ))->toArray();

        // Falls back to using the raw string as the key name.
        $this->assertArrayHasKey('{not valid json', $payload[0]['media'][0]);
    }

    public function test_dot_notation_header_produces_nested_object(): void
    {
        $exportMap = $this->buildExportMap([
            'columns' => ['price', 'currency_static'],
            'headers' => ['price.value', 'price.currency'],
            'config' => [
                'json_envelope' => '{ "results": "__RESULTS__" }',
                'column_types' => ['number', 'string'],
            ],
        ]);

        $payload = (new JsonMapExport(
            collect([['price' => 100, 'currency_static' => 'GBP']]),
            $exportMap
        ))->toArray();

        $this->assertSame(
            ['price' => ['value' => 100, 'currency' => 'GBP']],
            $payload['results'][0]
        );
    }

    public function test_deeply_nested_header_keys_compose(): void
    {
        $exportMap = $this->buildExportMap([
            'columns' => ['engine_size', 'cylinders'],
            'headers' => ['specs.engine.size', 'specs.engine.cylinders'],
            'config' => ['column_types' => ['number', 'number']],
        ]);

        $payload = (new JsonMapExport(
            collect([['engine_size' => 2.5, 'cylinders' => 4]]),
            $exportMap
        ))->toArray();

        $this->assertSame(
            ['specs' => ['engine' => ['size' => 2.5, 'cylinders' => 4]]],
            $payload[0]
        );
    }

    public function test_flat_and_nested_keys_coexist_in_same_row(): void
    {
        $exportMap = $this->buildExportMap([
            'columns' => ['uuid', 'price_val', 'price_cur'],
            'headers' => ['ad_reference', 'price.value', 'price.currency'],
            'config' => ['column_types' => ['string', 'number', 'string']],
        ]);

        $payload = (new JsonMapExport(
            collect([['uuid' => 'X', 'price_val' => 50, 'price_cur' => 'USD']]),
            $exportMap
        ))->toArray();

        $this->assertSame(
            ['ad_reference' => 'X', 'price' => ['value' => 50, 'currency' => 'USD']],
            $payload[0]
        );
    }

    public function test_header_falls_back_to_column_when_empty(): void
    {
        $exportMap = $this->buildExportMap([
            'columns' => ['uuid', 'manufacture_year'],
            'headers' => ['', null],
            'config' => ['column_types' => ['string', 'number']],
        ]);

        $payload = (new JsonMapExport(
            collect([['uuid' => 'X', 'manufacture_year' => 2022]]),
            $exportMap
        ))->toArray();

        $this->assertSame(['uuid' => 'X', 'manufacture_year' => 2022], $payload[0]);
    }

    public function test_dotted_column_source_falls_through_to_nested_output(): void
    {
        // When the header is empty, the column source becomes the JSON key,
        // and dot-notation in the source produces a nested object — useful when
        // the source path is already in the desired JSON shape.
        $exportMap = $this->buildExportMap([
            'columns' => ['make.name'],
            'headers' => [''],
            'config' => ['column_types' => ['string']],
        ]);

        $payload = (new JsonMapExport(
            collect([['make.name' => 'Toyota']]),
            $exportMap
        ))->toArray();

        $this->assertSame(['make' => ['name' => 'Toyota']], $payload[0]);
    }

    /**
     * Build an in-memory ExportMap (no DB persistence) for unit testing JsonMapExport.
     */
    private function buildExportMap(array $attrs): ExportMap
    {
        $map = new ExportMap();
        $map->format = 'json';
        $map->columns = $attrs['columns'] ?? [];
        $map->headers = $attrs['headers'] ?? [];
        $map->config = $attrs['config'] ?? [];
        return $map;
    }
}
