<?php

declare(strict_types=1);

namespace ApiModel\Service;

use ApiModel\Infrastructure\Persistence\Database;
use ApiModel\Shared\Exceptions\BadRequestException;
use ApiModel\Shared\Exceptions\ForbiddenException;
use ApiModel\Shared\Exceptions\NotFoundException;
use PDO;

final class CertificadoService
{
    private const PARROQUIAS_VALIDAS = [
        'Los Teques',
        'Altagracia de la Montaña',
        'Cecilio Acosta',
        'El Jarillo',
        'Paracotos',
        'San Pedro de los Altos',
        'Tácata',
    ];

    private const CERTIFICADO_SELECT = 'SELECT c.*,
                ct.id as tipo_id_join, ct.codigo as tipo_codigo, ct.nombre as tipo_nombre,
                ct.monto as tipo_monto, ct.color as tipo_color, ct.fondo_archivo as tipo_fondo_archivo,
                f.id as ferreteria_id_join, f.nombre as ferreteria_nombre, f.direccion as ferreteria_direccion,
                u.name as creador_nombre';

    private const CERTIFICADO_FROM = ' FROM public.certificados c
                LEFT JOIN public.certificado_tipos ct ON ct.id = c.tipo_id
                LEFT JOIN public.ferreterias f ON f.id = c.ferreteria_id
                LEFT JOIN auth.users u ON u.id = c.creado_por';

    public function __construct(private readonly PDO $pdo) {}

    /** @return list<array<string, mixed>> */
    public function listCertificados(array $filters): array
    {
        $sql = self::CERTIFICADO_SELECT . self::CERTIFICADO_FROM . ' WHERE 1=1';
        $params = [];

        if (!empty($filters['dni'])) {
            $sql .= ' AND c.beneficiario_dni ILIKE ?';
            $params[] = '%' . trim((string) $filters['dni']) . '%';
        }

        if (!empty($filters['codigo'])) {
            $sql .= ' AND c.codigo_barras ILIKE ?';
            $params[] = '%' . trim((string) $filters['codigo']) . '%';
        }

        if (!empty($filters['estado'])) {
            $sql .= ' AND c.estado = ?';
            $params[] = trim((string) $filters['estado']);
        }

        if (!empty($filters['ferreteria_id'])) {
            $sql .= ' AND c.ferreteria_id = ?';
            $params[] = trim((string) $filters['ferreteria_id']);
        }

        if (!empty($filters['tipo_id'])) {
            $sql .= ' AND c.tipo_id = ?';
            $params[] = trim((string) $filters['tipo_id']);
        }

        $sql .= ' ORDER BY c.created_at DESC';

        $st = $this->pdo->prepare($sql);
        $st->execute($params);

        return array_map($this->mapRow(...), $st->fetchAll());
    }

    /** @return array<string, mixed> */
    public function getCertificado(string $id): array
    {
        $row = $this->fetchCertificadoRow($id);

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

    /** @return array<string, mixed> */
    public function consultarParaCanje(string $id, string $userId): array
    {
        $row = $this->fetchCertificadoRow($id);
        $cert = $this->mapRow($row);
        $this->assertFerreteriaAutorizadaParaCanje($cert, $userId);

        return $cert;
    }

    /** @return array<string, mixed> */
    public function consultarParaCanjePorCodigo(string $codigo, string $userId): array
    {
        $row = $this->fetchCertificadoRowByCodigo($codigo);
        $cert = $this->mapRow($row);
        $this->assertFerreteriaAutorizadaParaCanje($cert, $userId);

        return $cert;
    }

    /** @param array<string, mixed> $data @return array<string, mixed> */
    public function createCertificado(array $data, string $creadoPor): array
    {
        $nombre = trim((string) ($data['beneficiario_nombre'] ?? ''));
        $dni = trim((string) ($data['beneficiario_dni'] ?? ''));
        $telefono = isset($data['beneficiario_telefono']) ? trim((string) $data['beneficiario_telefono']) : null;
        $parroquia = trim((string) ($data['parroquia'] ?? ''));
        $tipoId = trim((string) ($data['tipo_id'] ?? ''));
        $ferreteriaId = trim((string) ($data['ferreteria_id'] ?? ''));

        if ($nombre === '') {
            throw new BadRequestException('El nombre del beneficiario es obligatorio');
        }
        if ($dni === '') {
            throw new BadRequestException('El DNI/Cédula del beneficiario es obligatorio');
        }
        if ($parroquia === '') {
            throw new BadRequestException('La parroquia es obligatoria');
        }
        if (!in_array($parroquia, self::PARROQUIAS_VALIDAS, true)) {
            throw new BadRequestException('La parroquia seleccionada no es válida');
        }
        if ($tipoId === '') {
            throw new BadRequestException('El tipo de certificado es obligatorio');
        }

        $tipo = $this->getTipoActivo($tipoId);
        $tipoCodigo = strtoupper(trim((string) ($tipo['codigo'] ?? '')));
        $esTipoSinFerreteria = $tipoCodigo === 'D';

        if ($esTipoSinFerreteria) {
            $ferreteriaId = null;
            $monto = 0.0;
        } else {
            if ($ferreteriaId === '') {
                throw new BadRequestException('La ferretería asignada es obligatoria');
            }
            $this->assertFerreteriaActiva($ferreteriaId);

            // Monto libre del usuario; si no envía, usa el de referencia del tipo.
            if (array_key_exists('monto', $data) && $data['monto'] !== null && $data['monto'] !== '') {
                $monto = (float) $data['monto'];
            } else {
                $monto = (float) $tipo['monto'];
            }
            if ($monto <= 0) {
                throw new BadRequestException('El monto debe ser mayor a 0');
            }
        }

        $id = Database::uuid();
        $codigo = $this->generateNextCode();

        $this->pdo->prepare(
            'INSERT INTO public.certificados (
                id, codigo_barras, beneficiario_nombre, beneficiario_dni, beneficiario_telefono,
                parroquia, tipo_id, ferreteria_id, monto_inicial, monto_actual, estado, creado_por
            ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, \'ACTIVO\', ?)'
        )->execute([
            $id,
            $codigo,
            $nombre,
            $dni,
            $telefono,
            $parroquia,
            $tipoId,
            $ferreteriaId,
            $monto,
            $monto,
            $creadoPor,
        ]);

        return $this->getCertificado($id);
    }

    public function anularCertificado(string $id): array
    {
        $cert = $this->getCertificado($id);

        if ($cert['estado'] === 'ANULADO') {
            throw new BadRequestException('El certificado ya se encuentra anulado');
        }
        if ($cert['estado'] === 'AGOTADO') {
            throw new BadRequestException('No se puede anular un certificado que ya fue agotado (canjeado)');
        }

        $st = $this->pdo->prepare(
            'UPDATE public.certificados
             SET estado = \'ANULADO\', updated_at = CURRENT_TIMESTAMP
             WHERE id = ?'
        );
        $st->execute([$id]);

        return $this->getCertificado($id);
    }

    /** @param array<string, mixed> $cert */
    public function assertFerreteriaAutorizadaParaCanje(array $cert, string $userId): void
    {
        $ust = $this->pdo->prepare('SELECT ferreteria_id FROM auth.users WHERE id = ?');
        $ust->execute([$userId]);
        $userRow = $ust->fetch();

        if ($userRow === false) {
            throw new ForbiddenException('Usuario no válido');
        }

        $userFerreteriaId = $userRow['ferreteria_id'];
        if ($userFerreteriaId === null) {
            return;
        }

        // Tipo D / sin ferretería: no aplica validación de tienda
        if (empty($cert['ferreteria_id'])) {
            return;
        }

        if ((string) $cert['ferreteria_id'] !== (string) $userFerreteriaId) {
            $nombreFerreteria = (string) ($cert['ferreteria_nombre'] ?? 'otra ferretería');
            throw new BadRequestException(
                "Este certificado solo puede canjearse en la ferretería asignada: {$nombreFerreteria}"
            );
        }
    }

    /** @return array<string, mixed> */
    private function fetchCertificadoRow(string $id): array
    {
        $st = $this->pdo->prepare(self::CERTIFICADO_SELECT . self::CERTIFICADO_FROM . ' WHERE c.id = ?');
        $st->execute([$id]);
        $row = $st->fetch();

        if ($row === false) {
            throw new NotFoundException('Certificado no encontrado');
        }

        return $row;
    }

    /** @return array<string, mixed> */
    private function fetchCertificadoRowByCodigo(string $codigo): array
    {
        $codigo = strtoupper(trim(str_replace(' ', '', $codigo)));
        if ($codigo === '') {
            throw new BadRequestException('El número de certificado es obligatorio');
        }
        if (!str_starts_with($codigo, 'CERT-')) {
            $codigo = 'CERT-' . ltrim($codigo, '-');
        }
        if (!preg_match('/^CERT-\d+$/', $codigo)) {
            throw new BadRequestException('Formato de certificado inválido. Use por ejemplo CERT-10001');
        }

        $st = $this->pdo->prepare(
            self::CERTIFICADO_SELECT . self::CERTIFICADO_FROM . ' WHERE UPPER(c.codigo_barras) = ?'
        );
        $st->execute([$codigo]);
        $row = $st->fetch();

        if ($row === false) {
            throw new NotFoundException('Certificado no encontrado');
        }

        return $row;
    }

    /** @return array<string, mixed> */
    private function getTipoActivo(string $tipoId): array
    {
        $st = $this->pdo->prepare(
            'SELECT id, codigo, nombre, monto, color, fondo_archivo
             FROM public.certificado_tipos
             WHERE id = ? AND is_active = TRUE'
        );
        $st->execute([$tipoId]);
        $row = $st->fetch();

        if ($row === false) {
            throw new BadRequestException('El tipo de certificado seleccionado no es válido');
        }

        return $row;
    }

    private function assertFerreteriaActiva(string $ferreteriaId): void
    {
        $st = $this->pdo->prepare('SELECT nombre, is_active FROM public.ferreterias WHERE id = ?');
        $st->execute([$ferreteriaId]);
        $row = $st->fetch();

        if ($row === false || !Database::fromBool($row['is_active'])) {
            throw new BadRequestException('La ferretería asignada no está activa o no existe');
        }
    }

    private function generateNextCode(): string
    {
        $st = $this->pdo->query("SELECT MAX(CAST(SUBSTRING(codigo_barras FROM 6) AS INTEGER)) AS max_num FROM public.certificados WHERE codigo_barras LIKE 'CERT-%'");
        $row = $st->fetch();
        $maxNum = $row && $row['max_num'] !== null ? (int) $row['max_num'] : 0;

        return sprintf('CERT-%05d', $maxNum + 1);
    }

    /** @param array<string, mixed> $row @return array<string, mixed> */
    private function mapRow(array $row): array
    {
        return [
            'id' => $row['id'],
            'codigo_barras' => $row['codigo_barras'],
            'beneficiario_nombre' => $row['beneficiario_nombre'],
            'beneficiario_dni' => $row['beneficiario_dni'],
            'beneficiario_telefono' => $row['beneficiario_telefono'] ?? null,
            'parroquia' => $row['parroquia'] ?? null,
            'tipo_id' => $row['tipo_id'],
            'tipo_codigo' => $row['tipo_codigo'] ?? null,
            'tipo_nombre' => $row['tipo_nombre'] ?? null,
            'tipo_color' => $row['tipo_color'] ?? null,
            'tipo_fondo_archivo' => $row['tipo_fondo_archivo'] ?? null,
            'ferreteria_id' => $row['ferreteria_id'],
            'ferreteria_nombre' => $row['ferreteria_nombre'] ?? null,
            'ferreteria_direccion' => $row['ferreteria_direccion'] ?? null,
            'monto_inicial' => (float) $row['monto_inicial'],
            'monto_actual' => (float) $row['monto_actual'],
            'estado' => $row['estado'],
            'creado_por' => $row['creado_por'] ?? null,
            'creador_nombre' => $row['creador_nombre'] ?? 'Sistema',
            'created_at' => $row['created_at'],
            'updated_at' => $row['updated_at'],
        ];
    }
}
