<?php

declare(strict_types=1);

namespace ImovelZapAi\Importing\Application\Services;

use ImovelZapAi\Importing\Application\Data\ParsedProperty;
use ImovelZapAi\Properties\Domain\Enums\PropertyPurpose;
use ImovelZapAi\Properties\Domain\Enums\PropertyType;
use ImovelZapAi\Properties\Infrastructure\Persistence\Eloquent\CityModel;
use ImovelZapAi\Properties\Infrastructure\Persistence\Eloquent\NeighborhoodModel;
use ImovelZapAi\Properties\Infrastructure\Persistence\Eloquent\StateModel;

final class ImportMappingResolver
{
    /**
     * @param  list<ParsedProperty>  $properties
     * @param  array<string, mixed>  $manualMapping
     * @return array{
     *     mapping: array<string, mixed>,
     *     ambiguities: list<array<string, mixed>>,
     *     unknown_neighborhoods: list<string>
     * }
     */
    public function resolve(array $properties, array $manualMapping = []): array
    {
        $typeMap = $manualMapping['property_types'] ?? [];
        $purposeMap = $manualMapping['purposes'] ?? [];
        $stateMap = $manualMapping['states'] ?? [];
        $cityMap = $manualMapping['cities'] ?? [];
        $neighborhoodMap = $manualMapping['neighborhoods'] ?? [];

        $ambiguities = [];
        $unknownNeighborhoods = [];

        $autoTypes = [];
        $autoPurposes = [];
        $autoStates = [];
        $autoCities = [];
        $autoNeighborhoods = [];

        foreach ($properties as $property) {
            if ($property->propertyType !== null) {
                $key = $this->normalizeKey($property->propertyType);
                $autoTypes[$key] = $typeMap[$key] ?? $this->mapPropertyType($property->propertyType)?->value;
                if ($autoTypes[$key] === null) {
                    $ambiguities[] = [
                        'field' => 'property_type',
                        'raw' => $property->propertyType,
                        'key' => $key,
                    ];
                }
            }

            if ($property->transactionType !== null) {
                $key = $this->normalizeKey($property->transactionType);
                $autoPurposes[$key] = $purposeMap[$key] ?? $this->mapPurpose($property->transactionType)?->value;
                if ($autoPurposes[$key] === null) {
                    $ambiguities[] = [
                        'field' => 'purpose',
                        'raw' => $property->transactionType,
                        'key' => $key,
                    ];
                }
            }

            if ($property->state !== null) {
                $key = $this->normalizeKey($property->state);
                $autoStates[$key] = $stateMap[$key] ?? $this->findStateId($property->state);
                if ($autoStates[$key] === null) {
                    $ambiguities[] = [
                        'field' => 'state',
                        'raw' => $property->state,
                        'key' => $key,
                    ];
                }
            }

            if ($property->city !== null && $property->state !== null) {
                $stateKey = $this->normalizeKey($property->state);
                $cityKey = $this->normalizeKey($property->city);
                $composite = "{$stateKey}|{$cityKey}";
                $stateId = $autoStates[$stateKey] ?? null;
                $autoCities[$composite] = $cityMap[$composite] ?? ($stateId ? $this->findCityId((string) $stateId, $property->city) : null);
                if ($autoCities[$composite] === null) {
                    $ambiguities[] = [
                        'field' => 'city',
                        'raw' => $property->city,
                        'key' => $composite,
                        'state' => $property->state,
                    ];
                }
            }

            if ($property->neighborhood !== null && $property->city !== null && $property->state !== null) {
                $stateKey = $this->normalizeKey($property->state);
                $cityKey = $this->normalizeKey($property->city);
                $neighborhoodKey = $this->normalizeKey($property->neighborhood);
                $composite = "{$stateKey}|{$cityKey}|{$neighborhoodKey}";
                $cityId = $autoCities["{$stateKey}|{$cityKey}"] ?? null;
                $resolved = $neighborhoodMap[$composite] ?? ($cityId ? $this->findNeighborhoodName((string) $cityId, $property->neighborhood) : null);
                $autoNeighborhoods[$composite] = $resolved;
                if ($resolved === null) {
                    $unknownNeighborhoods[] = $property->neighborhood;
                    // Neighborhood can be created on import — not a hard ambiguity.
                }
            }
        }

        $uniqueAmbiguities = [];
        foreach ($ambiguities as $item) {
            $uniqueAmbiguities[$item['field'].'|'.$item['key']] = $item;
        }

        return [
            'mapping' => [
                'property_types' => array_filter($autoTypes, static fn ($v) => $v !== null),
                'purposes' => array_filter($autoPurposes, static fn ($v) => $v !== null),
                'states' => array_filter($autoStates, static fn ($v) => $v !== null),
                'cities' => array_filter($autoCities, static fn ($v) => $v !== null),
                'neighborhoods' => array_filter($autoNeighborhoods, static fn ($v) => $v !== null),
                'issues' => $manualMapping['issues'] ?? [],
            ],
            'ambiguities' => array_values($uniqueAmbiguities),
            'unknown_neighborhoods' => array_values(array_unique($unknownNeighborhoods)),
        ];
    }

    public function mapPropertyType(string $raw): ?PropertyType
    {
        $key = $this->normalizeKey($raw);

        return match ($key) {
            'residential apartment', 'apartment', 'apartamento' => PropertyType::Apartment,
            'residential home', 'home', 'house', 'casa' => PropertyType::House,
            'residential condo', 'condo', 'casa condominio', 'casa em condominio' => PropertyType::CondoHouse,
            'residential land lot', 'land', 'terreno' => PropertyType::Land,
            'commercial', 'comercial' => PropertyType::Commercial,
            'farm', 'sitio', 'fazenda', 'sitio fazenda' => PropertyType::Farm,
            'kitnet' => PropertyType::Kitnet,
            'studio' => PropertyType::Studio,
            'penthouse', 'cobertura' => PropertyType::Penthouse,
            default => PropertyType::tryFrom($key),
        };
    }

    public function mapPurpose(string $raw): ?PropertyPurpose
    {
        $key = $this->normalizeKey($raw);

        return match ($key) {
            'sale', 'for sale', 'venda' => PropertyPurpose::Sale,
            'rent', 'for rent', 'aluguel', 'locacao', 'locação' => PropertyPurpose::Rent,
            default => PropertyPurpose::tryFrom($key),
        };
    }

    public function normalizeKey(string $value): string
    {
        $value = mb_strtolower(trim($value));
        $value = strtr($value, [
            'á' => 'a', 'à' => 'a', 'ã' => 'a', 'â' => 'a',
            'é' => 'e', 'ê' => 'e',
            'í' => 'i',
            'ó' => 'o', 'ô' => 'o', 'õ' => 'o',
            'ú' => 'u',
            'ç' => 'c',
        ]);
        $value = str_replace(['/', '-', '_'], ' ', $value);
        $value = preg_replace('/\s+/', ' ', $value) ?? $value;

        return trim($value);
    }

    public function resolveStateId(string $raw): ?string
    {
        return $this->findStateId($raw);
    }

    public function resolveCityId(string $stateId, string $raw): ?string
    {
        return $this->findCityId($stateId, $raw);
    }

    private function findStateId(string $raw): ?string
    {
        $normalized = $this->normalizeKey($raw);
        $code = strtoupper(trim($raw));

        $byCode = StateModel::query()->where('code', $code)->first();
        if ($byCode !== null) {
            return $byCode->id;
        }

        return StateModel::query()
            ->get()
            ->first(fn (StateModel $state) => $this->normalizeKey($state->name) === $normalized)
            ?->id;
    }

    private function findCityId(string $stateId, string $raw): ?string
    {
        $normalized = $this->normalizeKey($raw);

        return CityModel::query()
            ->where('state_id', $stateId)
            ->get()
            ->first(fn (CityModel $city) => $this->normalizeKey($city->name) === $normalized)
            ?->id;
    }

    private function findNeighborhoodName(string $cityId, string $raw): ?string
    {
        $normalized = $this->normalizeKey($raw);

        $neighborhood = NeighborhoodModel::query()
            ->where('city_id', $cityId)
            ->get()
            ->first(fn (NeighborhoodModel $item) => $this->normalizeKey($item->name) === $normalized);

        return $neighborhood?->name;
    }
}
