<?php

declare(strict_types=1);

namespace App\Infrastructure\Consumer;

use App\Domain\Consumer\Consumer;
use DateTimeImmutable;
use DateTimeZone;
use Doctrine\DBAL\Connection;

/**
 * DBAL gateway for the consumers table.
 *
 * `policy_id` is RESTRICT-on-delete, so the FK protects the consumer's
 * referential integrity. We do not validate the policy exists here —
 * the controller looks it up first and returns 400 if missing.
 */
final class ConsumerRepository
{
    public function __construct(private readonly Connection $connection)
    {
    }

    public function findById(int $id): ?Consumer
    {
        /** @var array<string, mixed>|false $row */
        $row = $this->connection->fetchAssociative(
            'SELECT id, name, description, policy_id, is_active, audit_enabled, created_by_user_id, created_at, last_pulled_at '
            . 'FROM consumers WHERE id = :id',
            ['id' => $id]
        );

        return $row === false ? null : self::hydrate($row);
    }

    public function findByName(string $name): ?Consumer
    {
        /** @var array<string, mixed>|false $row */
        $row = $this->connection->fetchAssociative(
            'SELECT id, name, description, policy_id, is_active, audit_enabled, created_by_user_id, created_at, last_pulled_at '
            . 'FROM consumers WHERE name = :name',
            ['name' => $name]
        );

        return $row === false ? null : self::hydrate($row);
    }

    /**
     * @return list<Consumer>
     */
    public function list(int $limit, int $offset): array
    {
        /** @var list<array<string, mixed>> $rows */
        $rows = $this->connection->fetchAllAssociative(
            'SELECT id, name, description, policy_id, is_active, audit_enabled, created_by_user_id, created_at, last_pulled_at '
            . 'FROM consumers ORDER BY id DESC LIMIT :limit OFFSET :offset',
            ['limit' => $limit, 'offset' => $offset]
        );

        return array_map(static fn (array $r): Consumer => self::hydrate($r), $rows);
    }

    public function count(): int
    {
        return (int) $this->connection->fetchOne('SELECT COUNT(*) FROM consumers');
    }

    public function create(string $name, ?string $description, int $policyId, ?int $createdByUserId): int
    {
        $this->connection->insert('consumers', [
            'name' => $name,
            'description' => $description,
            'policy_id' => $policyId,
            'is_active' => 1,
            'audit_enabled' => 1,
            'created_by_user_id' => $createdByUserId,
        ]);

        return (int) $this->connection->lastInsertId();
    }

    /**
     * @param array<string, mixed> $fields
     */
    public function update(int $id, array $fields): void
    {
        if ($fields === []) {
            return;
        }
        $this->connection->update('consumers', $fields, ['id' => $id]);
    }

    public function softDelete(int $id): void
    {
        $this->connection->update('consumers', ['is_active' => 0], ['id' => $id]);
    }

    public function policyExists(int $policyId): bool
    {
        return (int) $this->connection->fetchOne(
            'SELECT COUNT(*) FROM policies WHERE id = :id',
            ['id' => $policyId]
        ) > 0;
    }

    /**
     * @param array<string, mixed> $row
     */
    private static function hydrate(array $row): Consumer
    {
        $createdAt = isset($row['created_at']) && $row['created_at'] !== null
            ? new DateTimeImmutable((string) $row['created_at'], new DateTimeZone('UTC'))
            : new DateTimeImmutable('now', new DateTimeZone('UTC'));
        $lastPulled = isset($row['last_pulled_at']) && $row['last_pulled_at'] !== null
            ? new DateTimeImmutable((string) $row['last_pulled_at'], new DateTimeZone('UTC'))
            : null;

        return new Consumer(
            id: (int) $row['id'],
            name: (string) $row['name'],
            description: $row['description'] !== null ? (string) $row['description'] : null,
            policyId: (int) $row['policy_id'],
            isActive: (bool) $row['is_active'],
            auditEnabled: (bool) ($row['audit_enabled'] ?? true),
            createdByUserId: $row['created_by_user_id'] !== null ? (int) $row['created_by_user_id'] : null,
            createdAt: $createdAt,
            lastPulledAt: $lastPulled,
        );
    }
}
