<?php

declare(strict_types=1);

namespace App\Framework\Notification\Storage;

use App\Framework\Core\ValueObjects\Timestamp;
use App\Framework\Database\ConnectionInterface;
use App\Framework\Database\ValueObjects\SqlQuery;
use App\Framework\DI\Attributes\DefaultImplementation;
use App\Framework\Notification\Notification;
use App\Framework\Notification\ValueObjects\NotificationChannel;
use App\Framework\Notification\ValueObjects\NotificationId;
use App\Framework\Notification\ValueObjects\NotificationPriority;
use App\Framework\Notification\ValueObjects\NotificationStatus;
use App\Framework\Notification\ValueObjects\NotificationType;

/**
 * Database implementation of NotificationRepository
 */
#[DefaultImplementation]
final readonly class DatabaseNotificationRepository implements NotificationRepository
{
    public function __construct(
        private ConnectionInterface $connection
    ) {}

    public function save(Notification $notification): void
    {
        $query = SqlQuery::create(<<<'SQL'
                INSERT INTO notifications (
                    id, recipient_id, type, title, body, data,
                    channels, priority, status, created_at, sent_at,
                    read_at, action_url, action_label
                ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
                ON CONFLICT (id) DO UPDATE SET
                    status = EXCLUDED.status,
                    sent_at = EXCLUDED.sent_at,
                    read_at = EXCLUDED.read_at
            SQL,
            [
                $notification->id->toString(),
                $notification->recipientId,
                $notification->type->toString(),
                $notification->title,
                $notification->body,
                json_encode($notification->data),
                json_encode(array_map(fn ($c) => $c->value, $notification->channels)),
                $notification->priority->value,
                $notification->status->value,
                $notification->createdAt->format('Y-m-d H:i:s'),
                $notification->sentAt?->format('Y-m-d H:i:s'),
                $notification->readAt?->format('Y-m-d H:i:s'),
                $notification->actionUrl,
                $notification->actionLabel,
            ]
        );

        $this->connection->execute($query);
    }

    public function findById(NotificationId $id): ?Notification
    {
        $query = SqlQuery::create(
             'SELECT * FROM notifications WHERE id = ?',
             [$id->toString()]
        );

        $row = $this->connection->queryOne($query);

        return $row ? $this->hydrateNotification($row) : null;
    }

    public function findByUser(string $userId, int $limit = 20, int $offset = 0): array
    {
        $query = SqlQuery::create(
             <<<'SQL'
                SELECT * FROM notifications
                WHERE recipient_id = ?
                ORDER BY created_at DESC
                LIMIT ? OFFSET ?
            SQL,
             [$userId, $limit, $offset]
        );

        $rows = $this->connection->query($query)->fetchAll();

        return array_map(fn ($row) => $this->hydrateNotification($row), $rows);
    }

    public function findUnreadByUser(string $userId, int $limit = 20): array
    {
        $query = SqlQuery::create(
             <<<'SQL'
                SELECT * FROM notifications
                WHERE recipient_id = ?
                AND status != ?
                ORDER BY created_at DESC
                LIMIT ?
            SQL,
             [$userId, NotificationStatus::READ->value, $limit]
        );

        $rows = $this->connection->query($query)->fetchAll();

        return array_map(fn ($row) => $this->hydrateNotification($row), $rows);
    }

    public function countUnreadByUser(string $userId): int
    {
        $query = SqlQuery::create(
             <<<'SQL'
                SELECT COUNT(*) as count FROM notifications
                WHERE recipient_id = ?
                AND status != ?
            SQL,
             [$userId, NotificationStatus::READ->value]
        );

        return (int) $this->connection->queryScalar($query);
    }

    public function markAsRead(NotificationId $id): bool
    {
        $query = SqlQuery::create(
             <<<'SQL'
                UPDATE notifications
                SET status = ?, read_at = ?
                WHERE id = ?
            SQL,
             [
                NotificationStatus::READ->value,
                 Timestamp::now()->format('Y-m-d H:i:s'),
                $id->toString(),
            ]
        );

        return $this->connection->execute($query) > 0;
    }

    public function markAllAsReadForUser(string $userId): int
    {
        $query = SqlQuery::create(
             <<<'SQL'
                UPDATE notifications
                SET status = ?, read_at = ?
                WHERE recipient_id = ?
                AND status != ?
            SQL,
             [
                NotificationStatus::READ->value,
                Timestamp::now()->format('Y-m-d H:i:s'),
                $userId,
                NotificationStatus::READ->value,
            ]
        );

        return $this->connection->execute($query);
    }

    public function delete(NotificationId $id): bool
    {
        $query = SqlQuery::create(
             'DELETE FROM notifications WHERE id = ?',
             [$id->toString()]
        );

        return $this->connection->execute($query) > 0;
    }

    public function deleteOldByStatus(NotificationStatus $status, int $daysOld): int
    {
        $cutoffDate = (new Timestamp())->modify("-{$daysOld} days");

        $query = SqlQuery::create(
             <<<'SQL'
                DELETE FROM notifications
                WHERE status = ?
                AND created_at < ?
            SQL,
             [
                $status->value,
                $cutoffDate->format('Y-m-d H:i:s'),
            ]
        );

        return $this->connection->execute($query);
    }

    private function hydrateNotification(array $row): Notification
    {
        $channels = array_map(
            fn ($c) => NotificationChannel::from($c),
            json_decode($row['channels'], true)
        );

        return new Notification(
            id         : NotificationId::fromString($row['id']),
            recipientId: $row['recipient_id'],
            type       : NotificationType::fromString($row['type']),
            title      : $row['title'],
            body       : $row['body'],
            createdAt  : Timestamp::fromTimestamp((int) strtotime($row['created_at'])),
            data       : json_decode($row['data'], true) ?? [],
            channels   : $channels,
            priority   : NotificationPriority::from($row['priority']),
            status     : NotificationStatus::from($row['status']),
            sentAt     : $row['sent_at'] ? Timestamp::fromTimestamp((int) strtotime($row['sent_at'])) : null,
            readAt     : $row['read_at'] ? Timestamp::fromTimestamp((int) strtotime($row['read_at'])) : null,
            actionUrl  : $row['action_url'],
            actionLabel: $row['action_label']
        );
    }
}
