<?php

declare(strict_types=1);

namespace ApiModel\Service;

use ApiModel\Domain\Repository\ContactRepositoryInterface;
use ApiModel\Infrastructure\Persistence\Database;
use ApiModel\Shared\Exceptions\BadRequestException;
use ApiModel\Shared\Exceptions\ConflictException;
use ApiModel\Shared\Exceptions\NotFoundException;

final class ContactService
{
    private const CONTACT_TYPES = ['customer', 'vendor', 'both'];

    public function __construct(private readonly ContactRepositoryInterface $contacts) {}

    /** @return list<array<string, mixed>> */
    public function listContacts(string $companyId, ?string $type = null): array
    {
        if ($type !== null) {
            $type = strtolower(trim($type));
            if (! in_array($type, self::CONTACT_TYPES, true)) {
                throw new BadRequestException('Tipo de contacto inválido');
            }
        }

        return array_map($this->mapContact(...), $this->contacts->listByCompany($companyId, $type));
    }

    /** @return array<string, mixed> */
    public function getContact(string $companyId, string $id): array
    {
        $row = $this->contacts->findById($id);
        if ($row === null || (string) $row['company_id'] !== $companyId) {
            throw new NotFoundException('Contacto no encontrado');
        }

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

    /** @param array<string, mixed> $data @return array<string, mixed> */
    public function createContact(string $companyId, array $data): array
    {
        $type = strtolower(trim((string) ($data['type'] ?? '')));
        $name = trim((string) ($data['name'] ?? ''));
        $taxId = isset($data['tax_id']) ? trim((string) $data['tax_id']) : null;
        if ($taxId === '') {
            $taxId = null;
        }
        $email = isset($data['email']) ? trim((string) $data['email']) : null;
        if ($email === '') {
            $email = null;
        }
        $phone = isset($data['phone']) ? trim((string) $data['phone']) : null;
        if ($phone === '') {
            $phone = null;
        }
        $address = isset($data['address']) ? trim((string) $data['address']) : null;
        if ($address === '') {
            $address = null;
        }

        $this->validateContactFields($type, $name, $email);

        if ($taxId !== null) {
            $existing = $this->contacts->findByTaxId($companyId, $taxId);
            if ($existing !== null) {
                throw new ConflictException('Ya existe un contacto con ese número de identificación (Tax ID)');
            }
        }

        $id = Database::uuid();
        $this->contacts->insert($id, $companyId, $type, $name, $taxId, $email, $phone, $address);

        $row = $this->contacts->findById($id);

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

    /** @param array<string, mixed> $data @return array<string, mixed> */
    public function updateContact(string $companyId, string $id, array $data): array
    {
        $row = $this->contacts->findById($id);
        if ($row === null || (string) $row['company_id'] !== $companyId) {
            throw new NotFoundException('Contacto no encontrado');
        }

        $type = strtolower(trim((string) ($data['type'] ?? $row['type'])));
        $name = trim((string) ($data['name'] ?? $row['name']));
        $taxId = array_key_exists('tax_id', $data) ? (trim((string) $data['tax_id']) ?: null) : ($row['tax_id'] ?: null);
        $email = array_key_exists('email', $data) ? (trim((string) $data['email']) ?: null) : ($row['email'] ?: null);
        $phone = array_key_exists('phone', $data) ? (trim((string) $data['phone']) ?: null) : ($row['phone'] ?: null);
        $address = array_key_exists('address', $data) ? (trim((string) $data['address']) ?: null) : ($row['address'] ?: null);
        $isActive = (bool) ($data['is_active'] ?? $row['is_active']);

        $this->validateContactFields($type, $name, $email);

        if ($taxId !== null) {
            $existing = $this->contacts->findByTaxId($companyId, $taxId);
            if ($existing !== null && (string) $existing['id'] !== $id) {
                throw new ConflictException('Ya existe otro contacto con ese número de identificación (Tax ID)');
            }
        }

        $this->contacts->update($id, $type, $name, $taxId, $email, $phone, $address, $isActive);

        return $this->mapContact($this->contacts->findById($id) ?? []);
    }

    public function deleteContact(string $companyId, string $id): void
    {
        $row = $this->contacts->findById($id);
        if ($row === null || (string) $row['company_id'] !== $companyId) {
            throw new NotFoundException('Contacto no encontrado');
        }

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

    /** @param array<string, mixed> $row @return array<string, mixed> */
    private function mapContact(array $row): array
    {
        return [
            'id' => $row['id'] ?? null,
            'company_id' => $row['company_id'] ?? null,
            'type' => $row['type'] ?? '',
            'name' => $row['name'] ?? '',
            'tax_id' => $row['tax_id'] ?? null,
            'email' => $row['email'] ?? null,
            'phone' => $row['phone'] ?? null,
            'address' => $row['address'] ?? null,
            'is_active' => (bool) ($row['is_active'] ?? true),
        ];
    }

    private function validateContactFields(string $type, string $name, ?string $email): void
    {
        if ($name === '') {
            throw new BadRequestException('El nombre es obligatorio');
        }
        if (! in_array($type, self::CONTACT_TYPES, true)) {
            throw new BadRequestException('Tipo de contacto inválido');
        }
        if ($email !== null && ! filter_var($email, FILTER_VALIDATE_EMAIL)) {
            throw new BadRequestException('El correo electrónico no es válido');
        }
    }
}
