<?php

declare(strict_types=1);

namespace ApiModel\Service;

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

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

    /** @return list<array<string, mixed>> */
    public function listFerreterias(?bool $activeOnly = null): array
    {
        $sql = '
            SELECT f.*,
                COALESCE((SELECT SUM(c.monto) FROM public.canjes c WHERE c.ferreteria_id = f.id), 0) as total_canjeado,
                COALESCE((SELECT SUM(p.monto) FROM public.pagos_ferreterias p WHERE p.ferreteria_id = f.id), 0) as total_pagado
            FROM public.ferreterias f
        ';
        $params = [];
        if ($activeOnly !== null) {
            $sql .= ' WHERE f.is_active = ?';
            $params[] = Database::bool($activeOnly);
        }
        $sql .= ' ORDER BY f.nombre ASC';

        $st = $this->pdo->prepare($sql);
        $st->execute($params);
        
        return array_map($this->mapRow(...), $st->fetchAll());
    }

    /** @return array<string, mixed> */
    public function getFerreteria(string $id): array
    {
        $st = $this->pdo->prepare('
            SELECT f.*,
                COALESCE((SELECT SUM(c.monto) FROM public.canjes c WHERE c.ferreteria_id = f.id), 0) as total_canjeado,
                COALESCE((SELECT SUM(p.monto) FROM public.pagos_ferreterias p WHERE p.ferreteria_id = f.id), 0) as total_pagado
            FROM public.ferreterias f
            WHERE f.id = ?
        ');
        $st->execute([$id]);
        $row = $st->fetch();

        if ($row === false) {
            throw new NotFoundException('Ferretería no encontrada');
        }

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

    /** @param array<string, mixed> $data @return array<string, mixed> */
    public function createFerreteria(array $data): array
    {
        $nombre = trim((string) ($data['nombre'] ?? ''));
        $rif = strtoupper(trim((string) ($data['rif'] ?? '')));
        $direccion = isset($data['direccion']) ? trim((string) $data['direccion']) : null;
        $telefono = isset($data['telefono']) ? trim((string) $data['telefono']) : null;

        $this->validateFields($nombre, $rif);

        // Check uniqueness of RIF
        $st = $this->pdo->prepare('SELECT 1 FROM public.ferreterias WHERE rif = ?');
        $st->execute([$rif]);
        if ($st->fetch()) {
            throw new ConflictException('Ya existe una ferretería registrada con ese RIF');
        }

        $id = Database::uuid();
        $this->pdo->prepare(
            'INSERT INTO public.ferreterias (id, nombre, rif, direccion, telefono, is_active)
             VALUES (?, ?, ?, ?, ?, TRUE)'
        )->execute([$id, $nombre, $rif, $direccion, $telefono]);

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

    /** @param array<string, mixed> $data @return array<string, mixed> */
    public function updateFerreteria(string $id, array $data): array
    {
        $row = $this->getFerreteria($id);

        $nombre = isset($data['nombre']) ? trim((string) $data['nombre']) : $row['nombre'];
        $rif = isset($data['rif']) ? strtoupper(trim((string) $data['rif'])) : $row['rif'];
        $direccion = array_key_exists('direccion', $data) ? $this->nullableString($data['direccion']) : $row['direccion'];
        $telefono = array_key_exists('telefono', $data) ? $this->nullableString($data['telefono']) : $row['telefono'];
        $isActive = array_key_exists('is_active', $data) || array_key_exists('activo', $data)
            ? (bool) ($data['is_active'] ?? $data['activo'] ?? true)
            : Database::fromBool($row['is_active']);

        $this->validateFields($nombre, $rif);

        // Check RIF uniqueness excluding self
        $st = $this->pdo->prepare('SELECT 1 FROM public.ferreterias WHERE rif = ? AND id <> ?');
        $st->execute([$rif, $id]);
        if ($st->fetch()) {
            throw new ConflictException('Ya existe otra ferretería registrada con ese RIF');
        }

        $this->pdo->prepare(
            'UPDATE public.ferreterias 
             SET nombre = ?, rif = ?, direccion = ?, telefono = ?, is_active = ?, updated_at = CURRENT_TIMESTAMP
             WHERE id = ?'
        )->execute([$nombre, $rif, $direccion, $telefono, Database::bool($isActive), $id]);

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

    private function validateFields(string $nombre, string $rif): void
    {
        if ($nombre === '') {
            throw new BadRequestException('El nombre es obligatorio');
        }
        if ($rif === '') {
            throw new BadRequestException('El RIF es obligatorio');
        }
    }

    private function nullableString(mixed $val): ?string
    {
        if ($val === null) {
            return null;
        }
        $trimmed = trim((string) $val);
        return $trimmed === '' ? null : $trimmed;
    }

    /** @param array<string, mixed> $row @return array<string, mixed> */
    private function mapRow(array $row): array
    {
        return [
            'id' => $row['id'],
            'nombre' => $row['nombre'],
            'rif' => $row['rif'],
            'direccion' => $row['direccion'] ?? null,
            'telefono' => $row['telefono'] ?? null,
            'is_active' => Database::fromBool($row['is_active']),
            'activo' => Database::fromBool($row['is_active']),
            'total_canjeado' => (float)($row['total_canjeado'] ?? 0.0),
            'total_pagado' => (float)($row['total_pagado'] ?? 0.0),
            'saldo_pendiente' => (float)(($row['total_canjeado'] ?? 0.0) - ($row['total_pagado'] ?? 0.0)),
        ];
    }
}
