<?php

declare(strict_types=1);

namespace ApiModel\Service;

use ApiModel\Domain\Repository\CurrencyRepositoryInterface;
use ApiModel\Domain\Repository\ExchangeRateRepositoryInterface;
use ApiModel\Shared\Exceptions\BadRequestException;
use ApiModel\Shared\Exceptions\NotFoundException;

final class ExchangeRateService
{
    public function __construct(
        private readonly ExchangeRateRepositoryInterface $repository,
        private readonly CurrencyRepositoryInterface $currencies,
    ) {}

    /** @return list<array<string, mixed>> */
    public function listExchangeRates(string $companyId): array
    {
        return array_map($this->mapExchangeRate(...), $this->repository->listByCompany($companyId));
    }

    /** @return array<string, mixed> */
    public function getExchangeRate(string $companyId, string $id): array
    {
        $row = $this->repository->findById($id);
        if ($row === null || (string) $row['company_id'] !== $companyId) {
            throw new NotFoundException('Tasa de cambio no encontrada');
        }

        return $this->mapExchangeRate($row);
    }

    /** @param array<string, mixed> $data @return array<string, mixed> */
    public function saveExchangeRate(string $companyId, array $data): array
    {
        $date = trim((string) ($data['rate_date'] ?? ''));
        $fromCurrency = strtoupper(trim((string) ($data['from_currency'] ?? '')));
        $toCurrency = strtoupper(trim((string) ($data['to_currency'] ?? '')));
        $rate = (float) ($data['rate'] ?? 0.0);

        $this->validateRateFields($date, $fromCurrency, $toCurrency, $rate);
        if (! $this->currencies->isActiveCode($fromCurrency)) {
            throw new BadRequestException("La moneda {$fromCurrency} no está configurada o está inactiva");
        }
        if (! $this->currencies->isActiveCode($toCurrency)) {
            throw new BadRequestException("La moneda {$toCurrency} no está configurada o está inactiva");
        }

        $this->repository->upsert($companyId, $date, $fromCurrency, $toCurrency, (string) $rate);

        $row = $this->repository->findRate($companyId, $date, $fromCurrency, $toCurrency);

        return $this->mapExchangeRate($row ?? []);
    }

    public function deleteExchangeRate(string $companyId, string $id): void
    {
        $row = $this->repository->findById($id);
        if ($row === null || (string) $row['company_id'] !== $companyId) {
            throw new NotFoundException('Tasa de cambio no encontrada');
        }

        $this->repository->delete($id);
    }

    /** @param array<string, mixed> $row @return array<string, mixed> */
    private function mapExchangeRate(array $row): array
    {
        return [
            'id' => $row['id'] ?? null,
            'company_id' => $row['company_id'] ?? null,
            'rate_date' => $row['rate_date'] ?? '',
            'from_currency' => $row['from_currency'] ?? '',
            'to_currency' => $row['to_currency'] ?? '',
            'rate' => (float) ($row['rate'] ?? 1.0),
            'created_at' => $row['created_at'] ?? null,
        ];
    }

    private function validateRateFields(string $date, string $from, string $to, float $rate): void
    {
        if ($date === '') {
            throw new BadRequestException('La fecha de la tasa es obligatoria');
        }
        if (!preg_match('/^\d{4}-\d{2}-\d{2}$/', $date)) {
            throw new BadRequestException('Formato de fecha inválido. Utilice AAAA-MM-DD');
        }
        if (strlen($from) !== 3 || strlen($to) !== 3) {
            throw new BadRequestException('Los códigos de moneda deben tener exactamente 3 caracteres');
        }
        if ($from === $to) {
            throw new BadRequestException('La moneda de origen y destino no pueden ser iguales');
        }
        if ($rate <= 0) {
            throw new BadRequestException('La tasa de cambio debe ser un valor mayor que cero');
        }
    }
}
