<?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;
use Exception;

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

    /** @return list<array<string, mixed>> */
    public function listCanjes(string $userId, array $filters): array
    {
        // Get user details to check ferreteria association
        $ust = $this->pdo->prepare('SELECT ferreteria_id FROM auth.users WHERE id = ?');
        $ust->execute([$userId]);
        $userRow = $ust->fetch();
        $userFerreteriaId = $userRow ? $userRow['ferreteria_id'] : null;

        $sql = 'SELECT c.*, 
                       f.nombre as ferreteria_nombre, 
                       cert.codigo_barras as certificado_codigo, 
                       cert.beneficiario_nombre, 
                       cert.beneficiario_dni,
                       u.name as usuario_nombre
                FROM public.canjes c
                JOIN public.ferreterias f ON f.id = c.ferreteria_id
                JOIN public.certificados cert ON cert.id = c.certificado_id
                JOIN auth.users u ON u.id = c.realizado_por
                WHERE 1=1';
        $params = [];

        // If user is tied to a specific Hardware Store, restrict listings to their own store
        if ($userFerreteriaId !== null) {
            $sql .= ' AND c.ferreteria_id = ?';
            $params[] = $userFerreteriaId;
        } elseif (!empty($filters['ferreteria_id'])) {
            // Admin filtering by store
            $sql .= ' AND c.ferreteria_id = ?';
            $params[] = trim((string)$filters['ferreteria_id']);
        }

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

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

        $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 registrarCanje(array $data, string $userId): array
    {
        $certificadoId = trim((string)($data['certificado_id'] ?? ''));
        $descripcion = isset($data['descripcion']) ? trim((string)$data['descripcion']) : null;
        $cedulaFoto = $this->normalizeCedulaFoto($data['beneficiario_cedula_foto'] ?? null);

        if ($certificadoId === '') {
            throw new BadRequestException('El ID del certificado es obligatorio');
        }

        if ($cedulaFoto === null) {
            throw new BadRequestException('La foto de la cédula/DNI del beneficiario es obligatoria');
        }

        // Get user details
        $ust = $this->pdo->prepare('SELECT ferreteria_id, email FROM auth.users WHERE id = ?');
        $ust->execute([$userId]);
        $userRow = $ust->fetch();
        
        if (!$userRow) {
            throw new ForbiddenException('Usuario no válido');
        }

        $ferreteriaId = $userRow['ferreteria_id'];

        // Fallback for Admin testing: if the user does not have a tied ferreteria_id but is Admin, they can pass it in the body
        if ($ferreteriaId === null && !empty($data['ferreteria_id'])) {
            $ferreteriaId = trim((string)$data['ferreteria_id']);
        }

        if ($ferreteriaId === null) {
            throw new BadRequestException('El usuario no está asociado a ninguna ferretería autorizada');
        }

        // Verify the store is active
        $fst = $this->pdo->prepare('SELECT is_active FROM public.ferreterias WHERE id = ?');
        $fst->execute([$ferreteriaId]);
        $ferrRow = $fst->fetch();
        if (!$ferrRow || !Database::fromBool($ferrRow['is_active'])) {
            throw new BadRequestException('La ferretería no está activa o autorizada en el sistema');
        }

        // START TRANSACTION FOR CONCURRENCY LOCK
        $this->pdo->beginTransaction();
        try {
            // LOCK ROW FOR UPDATE
            $stLock = $this->pdo->prepare('SELECT * FROM public.certificados WHERE id = ? FOR UPDATE');
            $stLock->execute([$certificadoId]);
            $cert = $stLock->fetch();

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

            $monto = (float)$cert['monto_inicial'];
            $montoActual = (float)$cert['monto_actual'];
            $estado = (string)$cert['estado'];

            // VALIDATE REDEEMABILITY
            if ($estado !== 'ACTIVO') {
                throw new BadRequestException("El certificado no se puede canjear porque se encuentra: $estado");
            }

            if ($montoActual !== $monto || $montoActual <= 0.0) {
                throw new BadRequestException('El certificado ya tiene consumos o un saldo inválido');
            }

            $certFerreteriaId = (string) ($cert['ferreteria_id'] ?? '');
            if ($certFerreteriaId === '') {
                throw new BadRequestException('El certificado no tiene una ferretería asignada');
            }

            if ((string) $ferreteriaId !== $certFerreteriaId) {
                $fstName = $this->pdo->prepare('SELECT nombre FROM public.ferreterias WHERE id = ?');
                $fstName->execute([$certFerreteriaId]);
                $ferrNameRow = $fstName->fetch();
                $nombreAsignada = $ferrNameRow ? (string) $ferrNameRow['nombre'] : 'la ferretería asignada';
                throw new BadRequestException("Este certificado solo puede canjearse en: {$nombreAsignada}");
            }

            // PERFORM EXCLUSIVE DEDUCTION (Canje Único Total)
            $stUpdate = $this->pdo->prepare(
                'UPDATE public.certificados 
                 SET monto_actual = 0.0, estado = \'AGOTADO\', updated_at = CURRENT_TIMESTAMP 
                 WHERE id = ?'
            );
            $stUpdate->execute([$certificadoId]);

            // INSERT REDEMPTION ENTRY
            $canjeId = Database::uuid();
            $stInsert = $this->pdo->prepare(
                'INSERT INTO public.canjes (id, certificado_id, ferreteria_id, monto, realizado_por, descripcion, beneficiario_cedula_foto)
                 VALUES (?, ?, ?, ?, ?, ?, ?)'
            );
            $stInsert->execute([
                $canjeId,
                $certificadoId,
                $ferreteriaId,
                $monto, // Full certificate value
                $userId,
                $descripcion,
                $cedulaFoto,
            ]);

            // COMMIT
            $this->pdo->commit();

            // Return receipt details
            return [
                'id' => $canjeId,
                'certificado_id' => $certificadoId,
                'ferreteria_id' => $ferreteriaId,
                'monto' => $monto,
                'realizado_por' => $userId,
                'descripcion' => $descripcion,
                'fecha_canje' => date('Y-m-d H:i:s')
            ];

        } catch (Exception $e) {
            $this->pdo->rollBack();
            throw $e;
        }
    }

    /** @param array<string, mixed> $row @return array<string, mixed> */
    private function mapRow(array $row): array
    {
        return [
            'id' => $row['id'],
            'certificado_id' => $row['certificado_id'],
            'certificado_codigo' => $row['certificado_codigo'] ?? '',
            'beneficiario_nombre' => $row['beneficiario_nombre'] ?? '',
            'beneficiario_dni' => $row['beneficiario_dni'] ?? '',
            'ferreteria_id' => $row['ferreteria_id'],
            'ferreteria_nombre' => $row['ferreteria_nombre'] ?? '',
            'monto' => (float)$row['monto'],
            'realizado_por' => $row['realizado_por'],
            'usuario_nombre' => $row['usuario_nombre'] ?? '',
            'descripcion' => $row['descripcion'] ?? null,
            'tiene_cedula_foto' => !empty($row['beneficiario_cedula_foto']),
            'created_at' => $row['created_at'],
        ];
    }

    private function normalizeCedulaFoto(mixed $photo): ?string
    {
        if ($photo === null) {
            return null;
        }

        $photo = trim((string)$photo);
        if ($photo === '') {
            return null;
        }

        if (!preg_match('#^data:image/(jpeg|jpg|png|webp);base64,#i', $photo)) {
            throw new BadRequestException('Formato de imagen de cédula inválido. Use JPEG, PNG o WEBP.');
        }

        $commaPos = strpos($photo, ',');
        if ($commaPos === false) {
            throw new BadRequestException('Imagen de cédula inválida');
        }

        $binary = base64_decode(substr($photo, $commaPos + 1), true);
        if ($binary === false) {
            throw new BadRequestException('Imagen de cédula inválida');
        }

        if (strlen($binary) > 1048576) {
            throw new BadRequestException('La foto de la cédula no debe superar 1 MB');
        }

        return $photo;
    }
}
