<?php

declare(strict_types=1);

namespace ImovelZapAi\Publishing\Connectors\GroupOlx\Application\Mappers;

use ImovelZapAi\Publishing\Connectors\GroupOlx\Application\Data\GroupOlxMappedListingData;
use ImovelZapAi\Publishing\Connectors\GroupOlx\Application\Ports\GroupOlxListingMapperInterface;
use ImovelZapAi\Publishing\Connectors\GroupOlx\Domain\Enums\GroupOlxPropertyType;
use ImovelZapAi\Publishing\Connectors\GroupOlx\Domain\Enums\GroupOlxTransactionType;
use ImovelZapAi\Publishing\Connectors\GroupOlx\Domain\Exceptions\GroupOlxMappingException;
use ImovelZapAi\Publishing\Domain\Canonical\NormalizedListing;
use ImovelZapAi\Publishing\Domain\Canonical\NormalizedListingCharacteristic;
use ImovelZapAi\Publishing\Domain\Canonical\NormalizedListingPhoto;
use ImovelZapAi\Publishing\Domain\Feeds\VrSync\ListingDataFeedDetails;
use ImovelZapAi\Publishing\Domain\Feeds\VrSync\ListingDataFeedListing;
use ImovelZapAi\Publishing\Domain\Feeds\VrSync\ListingDataFeedLocation;
use ImovelZapAi\Publishing\Domain\Feeds\VrSync\ListingDataFeedMediaItem;
use ImovelZapAi\Publishing\Domain\Feeds\VrSync\VrSyncEnumerations;

/**
 * NormalizedListing → DTO VRSync (Grupo OLX).
 *
 * Aplica enums oficiais, valida obrigatórios e não gera XML.
 */
final class GroupOlxListingMapper implements GroupOlxListingMapperInterface
{
    public function map(NormalizedListing $listing): GroupOlxMappedListingData
    {
        $errors = $this->validateRequired($listing);

        $transactionType = GroupOlxTransactionType::tryFromPurpose($listing->purpose);
        if ($transactionType === null) {
            $errors[] = 'Purpose/TransactionType inválido. Use Venda, Aluguel ou Sale/Rent.';
        }

        $propertyType = GroupOlxPropertyType::tryFromInternal($listing->type);
        if ($propertyType === null) {
            $errors[] = "PropertyType \"{$listing->type}\" não mapeado para VRSync.";
        }

        $currency = strtoupper(trim($listing->currency));
        if (! VrSyncEnumerations::isCurrency($currency)) {
            $errors[] = 'Currency deve ser BRL.';
        }

        if ($errors !== []) {
            throw GroupOlxMappingException::withErrors($errors);
        }

        /** @var GroupOlxTransactionType $transactionType */
        /** @var GroupOlxPropertyType $propertyType */
        $now = now()->format('Y-m-d\TH:i:s');
        $address = $listing->address;
        $characteristics = $this->characteristicMap($listing->characteristics);
        $formattedPrice = number_format((float) $listing->price, 2, '.', '');
        $listPrice = $transactionType->usesListPrice() ? $formattedPrice : null;
        $rentalPrice = $transactionType->usesRentalPrice() ? $formattedPrice : null;

        $vrSyncListing = new ListingDataFeedListing(
            listingId: trim($listing->code),
            title: trim($listing->title),
            transactionType: $transactionType->value,
            purpose: $transactionType->label(),
            status: trim($listing->status),
            details: new ListingDataFeedDetails(
                propertyType: $propertyType->value,
                description: trim($listing->description),
                listPrice: $listPrice,
                currency: $currency,
                bedrooms: $characteristics['bedrooms'] ?? null,
                suites: $characteristics['suites'] ?? null,
                bathrooms: $characteristics['bathrooms'] ?? null,
                garage: $characteristics['parking_spaces'] ?? null,
                livingArea: $characteristics['useful_area'] ?? null,
                lotArea: $characteristics['total_area'] ?? null,
                condominiumFee: $characteristics['condominium_fee'] ?? null,
                iptu: $characteristics['iptu'] ?? null,
            ),
            location: new ListingDataFeedLocation(
                country: $this->country($address->country),
                state: trim($address->state),
                city: trim($address->city),
                neighborhood: trim($address->neighborhood),
                postalCode: trim($address->postalCode),
                street: trim($address->street),
                number: trim($address->number),
                complement: trim($address->complement),
                address: $address->formatted(),
                latitude: $address->latitude,
                longitude: $address->longitude,
            ),
            media: $this->media($listing),
            detailUrl: $this->optionalUrl($listing->detailUrl),
            listDate: $now,
            lastUpdateDate: $now,
        );

        return new GroupOlxMappedListingData(
            listing: $vrSyncListing,
            transactionType: $transactionType,
            propertyType: $propertyType,
            usageType: $propertyType->usageType(),
            listPrice: $listPrice,
            rentalPrice: $rentalPrice,
        );
    }

    public function mapMany(array $listings): array
    {
        return array_map(
            fn (NormalizedListing $listing): GroupOlxMappedListingData => $this->map($listing),
            $listings,
        );
    }

    /** @return list<string> */
    private function validateRequired(NormalizedListing $listing): array
    {
        $errors = [];

        if (trim($listing->code) === '') {
            $errors[] = 'ListingID é obrigatório.';
        }

        if (trim($listing->title) === '') {
            $errors[] = 'Title é obrigatório.';
        }

        if (trim($listing->description) === '') {
            $errors[] = 'Description é obrigatória.';
        }

        if ($listing->price === null || trim($listing->price) === '' || (float) $listing->price <= 0) {
            $errors[] = 'Preço válido é obrigatório.';
        }

        if (trim($listing->type) === '') {
            $errors[] = 'PropertyType é obrigatório.';
        }

        if (trim($listing->purpose) === '') {
            $errors[] = 'Purpose/TransactionType é obrigatório.';
        }

        if (trim($listing->address->city) === '') {
            $errors[] = 'City é obrigatória.';
        }

        if (trim($listing->address->state) === '') {
            $errors[] = 'State é obrigatório.';
        }

        if ($listing->detailUrl !== null && trim($listing->detailUrl) !== '' && ! $this->isValidUrl($listing->detailUrl)) {
            $errors[] = 'DetailUrl deve ser uma URL válida.';
        }

        foreach ($listing->photos as $index => $photo) {
            if (! $this->isValidUrl($photo->url)) {
                $errors[] = 'Media #'.($index + 1).': URL inválida.';
            }
        }

        return $errors;
    }

    /**
     * @param  list<NormalizedListingCharacteristic>  $characteristics
     * @return array<string, string>
     */
    private function characteristicMap(array $characteristics): array
    {
        $map = [];

        foreach ($characteristics as $characteristic) {
            $value = trim($characteristic->value);

            if ($value === '') {
                continue;
            }

            $map[$characteristic->key] = $value;
        }

        return $map;
    }

    /**
     * @return list<ListingDataFeedMediaItem>
     */
    private function media(NormalizedListing $listing): array
    {
        return array_values(array_map(
            static fn (NormalizedListingPhoto $photo): ListingDataFeedMediaItem => new ListingDataFeedMediaItem(
                url: trim($photo->url),
                isPrimary: $photo->isPrimary,
                caption: $photo->caption !== null ? trim($photo->caption) : null,
            ),
            $listing->photos,
        ));
    }

    private function country(string $country): string
    {
        $normalized = strtoupper(trim($country));

        return match ($normalized) {
            '', 'BR', 'BRA', 'BRASIL' => 'Brasil',
            default => trim($country),
        };
    }

    private function optionalUrl(?string $url): ?string
    {
        if ($url === null || trim($url) === '') {
            return null;
        }

        return trim($url);
    }

    private function isValidUrl(string $url): bool
    {
        return filter_var(trim($url), FILTER_VALIDATE_URL) !== false
            && in_array(parse_url(trim($url), PHP_URL_SCHEME), ['http', 'https'], true);
    }
}
