<?php

declare(strict_types=1);

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

    public function countUnread(string $userId): int
    {
        $statement = $this->pdo->prepare(
            'SELECT COUNT(*)
             FROM dashboard_messages
             WHERE to_user_id = :to_user_id
               AND read_at IS NULL'
        );
        $statement->execute([':to_user_id' => $userId]);

        return (int) $statement->fetchColumn();
    }

    /**
     * @return list<array<string, string|null>>
     */
    public function listInbox(string $userId): array
    {
        $statement = $this->pdo->prepare(
            'SELECT dashboard_messages.*,
                    users.display_name AS from_display_name
             FROM dashboard_messages
             INNER JOIN users ON users.id = dashboard_messages.from_user_id
             WHERE dashboard_messages.to_user_id = :to_user_id
             ORDER BY dashboard_messages.created_at DESC, dashboard_messages.id DESC'
        );
        $statement->execute([':to_user_id' => $userId]);

        return array_map([$this, 'normalizeMessageRow'], $statement->fetchAll(PDO::FETCH_ASSOC) ?: []);
    }

    /**
     * @return list<array<string, string|null>>
     */
    public function listOutbox(string $userId): array
    {
        $statement = $this->pdo->prepare(
            'SELECT dashboard_messages.*,
                    users.display_name AS to_display_name
             FROM dashboard_messages
             INNER JOIN users ON users.id = dashboard_messages.to_user_id
             WHERE dashboard_messages.from_user_id = :from_user_id
             ORDER BY dashboard_messages.created_at DESC, dashboard_messages.id DESC'
        );
        $statement->execute([':from_user_id' => $userId]);

        return array_map([$this, 'normalizeMessageRow'], $statement->fetchAll(PDO::FETCH_ASSOC) ?: []);
    }

    public function send(string $fromUserId, string $toUserId, string $body): string
    {
        $body = trim($body);
        if ($toUserId === '' || $toUserId === $fromUserId) {
            throw new InvalidArgumentException('Choose another user to message.');
        }
        if ($body === '') {
            throw new InvalidArgumentException('Message body is required.');
        }

        $this->assertUserExists($toUserId);
        $id = myronGenerateId('msg');
        $statement = $this->pdo->prepare(
            'INSERT INTO dashboard_messages (id, from_user_id, to_user_id, body, read_at, created_at)
             VALUES (:id, :from_user_id, :to_user_id, :body, NULL, :created_at)'
        );
        $statement->execute([
            ':id' => $id,
            ':from_user_id' => $fromUserId,
            ':to_user_id' => $toUserId,
            ':body' => $body,
            ':created_at' => myronNow(),
        ]);

        return $id;
    }

    public function markAsRead(string $messageId, string $toUserId): void
    {
        $statement = $this->pdo->prepare(
            'UPDATE dashboard_messages
             SET read_at = COALESCE(read_at, :read_at)
             WHERE id = :id
               AND to_user_id = :to_user_id'
        );
        $statement->execute([
            ':read_at' => myronNow(),
            ':id' => $messageId,
            ':to_user_id' => $toUserId,
        ]);
    }

    /**
     * @param array<string, mixed> $row
     * @return array<string, string|null>
     */
    private function normalizeMessageRow(array $row): array
    {
        $normalized = [];
        foreach ($row as $key => $value) {
            $normalized[(string) $key] = $value === null ? null : (string) $value;
        }

        return $normalized;
    }

    private function assertUserExists(string $userId): void
    {
        $statement = $this->pdo->prepare('SELECT id FROM users WHERE id = :id LIMIT 1');
        $statement->execute([':id' => $userId]);
        if ($statement->fetchColumn() === false) {
            throw new InvalidArgumentException('Selected user does not exist.');
        }
    }
}
