<?php

namespace Tests\Unit\Services;

use App\Exceptions\ImportEstacionesException;
use App\Services\EstacionesImportService;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Support\Facades\DB;
use Tests\Support\CreatesStationImportExcel;
use Tests\TestCase;

class EstacionesImportServiceTest extends TestCase
{
    use CreatesStationImportExcel;
    use RefreshDatabase;

    /**
     * Verifica el camino feliz y que una ciudad inexistente no interrumpa todo
     * el proceso de importación.
     */
    public function test_imports_valid_rows_and_skips_unknown_cities(): void
    {
        $this->createCatalogCity('Asunción', 'Capital');

        DB::query()->from('estaciones')->insert([
            'titulo' => 'Anterior',
            'slug' => 'anterior',
            'estado' => true,
            'ciudad_id' => 1,
            'ubicacion' => json_encode(['lat' => -25.3, 'lng' => -57.6]),
            'direccion' => 'Registro anterior',
            'created_at' => now(),
            'updated_at' => now(),
        ]);

        $service = app(EstacionesImportService::class);
        $file = $this->createStationsExcel([
            [1, 'Avda. Primer Presidente', '-25.25595, -57.57174', 'Asunción', 'Capital', '', '', '', '', ''],
            [2, 'Sin ciudad válida', '-25.25595, -57.57174', 'Ciudad Falsa', 'Capital', '', '', '', '', ''],
        ]);

        $summary = $service->import($file, $this->mapping());

        $this->assertSame(2, $summary['total_rows']);
        $this->assertSame(1, $summary['imported_rows']);
        $this->assertSame(1, $summary['skipped_rows']);
        $this->assertDatabaseCount('estaciones', 1);
        $this->assertDatabaseHas('estaciones', [
            'titulo' => '',
            'direccion' => 'Avda. Primer Presidente',
        ]);
        $this->assertSame(
            'No se encontró una ciudad normalizada que coincida con la fila.',
            $summary['skipped'][0]['reason']
        );
    }

    /**
     * Tolera variantes razonables del nombre de ciudad cuando el departamento
     * deja un único candidato claramente reconocible.
     */
    public function test_matches_similar_city_names_within_the_same_department(): void
    {
        $this->createCatalogCity('Presidente Franco', 'Alto Paraná');

        $service = app(EstacionesImportService::class);
        $file = $this->createStationsExcel([
            [1, 'Ruta PY02', '-25.50910, -54.61120', 'Pdte Franco', 'Alto Paraná', '', '', '', '', ''],
        ]);

        $summary = $service->import($file, $this->mapping());

        $this->assertSame(1, $summary['imported_rows']);
        $this->assertSame(0, $summary['skipped_rows']);
        $this->assertDatabaseHas('estaciones', [
            'direccion' => 'Ruta PY02',
        ]);
    }

    /**
     * Resuelve alias operativos frecuentes contra el catálogo oficial sin
     * depender de heurísticas de similitud.
     */
    public function test_matches_configured_city_aliases(): void
    {
        $service = app(EstacionesImportService::class);

        $cases = [
            ['San Estanislao', 'San Pedro', 'Ruta 3, Km. 154', 'Santaní'],
            ['Mariscal José Félix Estigarribia', 'Boquerón', 'Fortín Boquerón', 'Mcal. Estigarribia'],
            ['General Elizardo Aquino', 'San Pedro', 'Ruta PY08', 'General Aquino'],
            ['Doctor J. Eulogio Estigarribia', 'Caaguazú', 'Ruta PY02', 'Campo 9'],
            ['General Higinio Morínigo', 'Caazapá', 'Barrio Centro', 'General Morínigo'],
            ['Tomás Romero Pereira', 'Itapúa', 'Ruta PY06', 'María Auxiliadora'],
        ];

        foreach ($cases as [$city, $department, $address, $alias]) {
            $cityId = $this->createCatalogCity($city, $department);
            $this->createCityAlias($cityId, $alias);

            $file = $this->createStationsExcel([
                [1, $address, '-25.25595, -57.57174', $alias, $department, '', '', '', '', ''],
            ]);

            $summary = $service->import($file, $this->mapping());

            $this->assertSame(1, $summary['imported_rows']);
            $this->assertSame(0, $summary['skipped_rows']);
            $this->assertDatabaseHas('estaciones', [
                'direccion' => $address,
            ]);

            DB::query()->from('estaciones')->delete();
        }
    }

    /**
     * Asegura que un encabezado diferente falle antes de tocar datos vigentes.
     */
    public function test_rejects_excel_with_invalid_headers(): void
    {
        $spreadsheet = new \PhpOffice\PhpSpreadsheet\Spreadsheet();
        $sheet = $spreadsheet->getActiveSheet();
        $sheet->setCellValue('A1', 'Cabecera incorrecta');
        $sheet->setCellValue('A2', '1');
        $sheet->setCellValue('B2', 'Fila cualquiera');
        $path = tempnam(sys_get_temp_dir(), 'stations-invalid-');
        (new \PhpOffice\PhpSpreadsheet\Writer\Xlsx($spreadsheet))->save($path);

        $file = new \Illuminate\Http\UploadedFile(
            $path,
            'cabeceras-invalidas.xlsx',
            'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
            null,
            true
        );

        $this->expectException(ImportEstacionesException::class);
        $this->expectExceptionMessage(
            'El archivo no tiene el formato esperado para importar estaciones.'
        );

        app(EstacionesImportService::class)->import($file, $this->mapping());
    }

    /**
     * Permite recuperar omitidas corregidas sin volver a borrar el lote ya
     * importado correctamente.
     */
    public function test_imports_corrected_skipped_rows_without_replacing_existing_data(): void
    {
        $this->createCatalogCity('Asunción', 'Capital');
        $this->createCatalogCity('Pedro Juan Caballero', 'Amambay');

        DB::query()->from('estaciones')->insert([
            'titulo' => '',
            'slug' => 'avda-primer-presidente',
            'estado' => true,
            'ciudad_id' => 1,
            'ubicacion' => json_encode(['lat' => -25.3, 'lng' => -57.6]),
            'direccion' => 'Avda. Primer Presidente',
            'created_at' => now(),
            'updated_at' => now(),
        ]);

        $summary = app(EstacionesImportService::class)->importSkippedRows([
            [
                'row' => 21,
                'direccion' => 'Ruta 5, Km. 1',
                'coordenadas' => '-22.56323, -55.71883',
                'ciudad_id' => 2,
                'ciudad' => 'Pedro Juan Caballero',
                'departamento' => 'Amambay',
            ],
        ]);

        $this->assertSame(1, $summary['imported_rows']);
        $this->assertSame(0, $summary['skipped_rows']);
        $this->assertDatabaseCount('estaciones', 2);
        $this->assertDatabaseHas('estaciones', [
            'direccion' => 'Ruta 5, Km. 1',
            'ciudad_id' => 2,
        ]);
    }

    /**
     * Crea el catálogo mínimo necesario para el match normalizado de ciudad.
     */
    private function createCatalogCity(string $city, string $department): int
    {
        $departmentId = DB::query()->from('departamentos')->insertGetId([
            'nombre' => $department,
            'slug' => str_slug($department),
            'estado' => true,
            'created_at' => now(),
            'updated_at' => now(),
        ]);

        return DB::query()->from('ciudades')->insertGetId([
            'nombre' => $city,
            'slug' => str_slug($city),
            'latitud' => -25.30,
            'longitud' => -57.60,
            'departamento_id' => $departmentId,
            'created_at' => now(),
            'updated_at' => now(),
        ]);
    }

    /**
     * Registra aliases en base de datos para que la resolución no dependa de
     * equivalencias hardcodeadas dentro del servicio.
     */
    private function createCityAlias(int $cityId, string $alias): void
    {
        DB::query()->from('ciudades_aliases')->insert([
            'ciudad_id' => $cityId,
            'nombre' => $alias,
            'slug' => str_slug($alias),
            'created_at' => now(),
            'updated_at' => now(),
        ]);
    }

    /**
     * Replica el contrato que envía el frontend al confirmar el mapeo.
     */
    private function mapping(): array
    {
        return [
            'direccion' => 'direccion_yo_localidad',
            'coordenadas' => 'coordenadas',
            'ciudad' => 'ciudad',
            'departamento' => 'departamento',
        ];
    }
}
