<?php

declare(strict_types=1);

namespace App\Framework\Notification;

use App\Framework\Core\ValueObjects\Timestamp;
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\NotificationTypeInterface;

/**
 * Core notification entity
 *
 * Immutable value object representing a notification
 */
final readonly class Notification
{
    /**
     * @param NotificationId $id Unique notification identifier
     * @param string $recipientId User/Entity receiving the notification
     * @param NotificationTypeInterface $type Notification category
     * @param string $title Notification title
     * @param string $body Notification message body
     * @param Timestamp $createdAt Creation timestamp
     * @param array<string, mixed> $data Additional structured data
     * @param array<NotificationChannel> $channels Delivery channels
     * @param NotificationPriority $priority Delivery priority
     * @param NotificationStatus $status Current status
     * @param Timestamp|null $sentAt Delivery timestamp
     * @param Timestamp|null $readAt Read timestamp
     * @param string|null $actionUrl Optional action URL
     * @param string|null $actionLabel Optional action button label
     */
    public function __construct(
        public NotificationId $id,
        public string $recipientId,
        public NotificationTypeInterface $type,
        public string $title,
        public string $body,
        public Timestamp $createdAt,
        public array $data = [],
        public array $channels = [],
        public NotificationPriority $priority = NotificationPriority::NORMAL,
        public NotificationStatus $status = NotificationStatus::PENDING,
        public ?Timestamp $sentAt = null,
        public ?Timestamp $readAt = null,
        public ?string $actionUrl = null,
        public ?string $actionLabel = null
    ) {
        if (empty($recipientId)) {
            throw new \InvalidArgumentException('Recipient ID cannot be empty');
        }

        if (empty($title)) {
            throw new \InvalidArgumentException('Title cannot be empty');
        }

        if (empty($body)) {
            throw new \InvalidArgumentException('Body cannot be empty');
        }

        if (empty($channels)) {
            throw new \InvalidArgumentException('At least one delivery channel is required');
        }
    }

    public static function create(
        string $recipientId,
        NotificationTypeInterface $type,
        string $title,
        string $body,
        NotificationChannel ...$channels
    ): self {
        return new self(
            id: NotificationId::generate(),
            recipientId: $recipientId,
            type: $type,
            title: $title,
            body: $body,
            createdAt: Timestamp::now(),
            data: [],
            channels: $channels,
            priority: NotificationPriority::NORMAL,
            status: NotificationStatus::PENDING
        );
    }

    public function withData(array $data): self
    {
        return new self(
            id: $this->id,
            recipientId: $this->recipientId,
            type: $this->type,
            title: $this->title,
            body: $this->body,
            createdAt: $this->createdAt,
            data: [...$this->data, ...$data],
            channels: $this->channels,
            priority: $this->priority,
            status: $this->status,
            sentAt: $this->sentAt,
            readAt: $this->readAt,
            actionUrl: $this->actionUrl,
            actionLabel: $this->actionLabel
        );
    }

    public function withPriority(NotificationPriority $priority): self
    {
        return new self(
            id: $this->id,
            recipientId: $this->recipientId,
            type: $this->type,
            title: $this->title,
            body: $this->body,
            createdAt: $this->createdAt,
            data: $this->data,
            channels: $this->channels,
            priority: $priority,
            status: $this->status,
            sentAt: $this->sentAt,
            readAt: $this->readAt,
            actionUrl: $this->actionUrl,
            actionLabel: $this->actionLabel
        );
    }

    public function withAction(string $url, string $label): self
    {
        return new self(
            id: $this->id,
            recipientId: $this->recipientId,
            type: $this->type,
            title: $this->title,
            body: $this->body,
            createdAt: $this->createdAt,
            data: $this->data,
            channels: $this->channels,
            priority: $this->priority,
            status: $this->status,
            sentAt: $this->sentAt,
            readAt: $this->readAt,
            actionUrl: $url,
            actionLabel: $label
        );
    }

    public function markAsSent(): self
    {
        return new self(
            id: $this->id,
            recipientId: $this->recipientId,
            type: $this->type,
            title: $this->title,
            body: $this->body,
            createdAt: $this->createdAt,
            data: $this->data,
            channels: $this->channels,
            priority: $this->priority,
            status: NotificationStatus::SENT,
            sentAt: Timestamp::now(),
            readAt: $this->readAt,
            actionUrl: $this->actionUrl,
            actionLabel: $this->actionLabel
        );
    }

    public function markAsDelivered(): self
    {
        return new self(
            id: $this->id,
            recipientId: $this->recipientId,
            type: $this->type,
            title: $this->title,
            body: $this->body,
            createdAt: $this->createdAt,
            data: $this->data,
            channels: $this->channels,
            priority: $this->priority,
            status: NotificationStatus::DELIVERED,
            sentAt: $this->sentAt ?? Timestamp::now(),
            readAt: $this->readAt,
            actionUrl: $this->actionUrl,
            actionLabel: $this->actionLabel
        );
    }

    public function markAsRead(): self
    {
        return new self(
            id: $this->id,
            recipientId: $this->recipientId,
            type: $this->type,
            title: $this->title,
            body: $this->body,
            createdAt: $this->createdAt,
            data: $this->data,
            channels: $this->channels,
            priority: $this->priority,
            status: NotificationStatus::READ,
            sentAt: $this->sentAt,
            readAt: Timestamp::now(),
            actionUrl: $this->actionUrl,
            actionLabel: $this->actionLabel
        );
    }

    public function markAsFailed(): self
    {
        return new self(
            id: $this->id,
            recipientId: $this->recipientId,
            type: $this->type,
            title: $this->title,
            body: $this->body,
            createdAt: $this->createdAt,
            data: $this->data,
            channels: $this->channels,
            priority: $this->priority,
            status: NotificationStatus::FAILED,
            sentAt: $this->sentAt,
            readAt: $this->readAt,
            actionUrl: $this->actionUrl,
            actionLabel: $this->actionLabel
        );
    }

    public function isRead(): bool
    {
        return $this->status === NotificationStatus::READ;
    }

    public function hasAction(): bool
    {
        return $this->actionUrl !== null;
    }

    public function supportsChannel(NotificationChannel $channel): bool
    {
        foreach ($this->channels as $supportedChannel) {
            if ($supportedChannel === $channel) {
                return true;
            }
        }

        return false;
    }

    public function toArray(): array
    {
        return [
            'id' => $this->id->toString(),
            'recipient_id' => $this->recipientId,
            'type' => $this->type->toString(),
            'title' => $this->title,
            'body' => $this->body,
            'data' => $this->data,
            'channels' => array_map(fn ($c) => $c->value, $this->channels),
            'priority' => $this->priority->value,
            'status' => $this->status->value,
            'created_at' => $this->createdAt->format('Y-m-d H:i:s'),
            'sent_at' => $this->sentAt?->format('Y-m-d H:i:s'),
            'read_at' => $this->readAt?->format('Y-m-d H:i:s'),
            'action_url' => $this->actionUrl,
            'action_label' => $this->actionLabel,
        ];
    }
}
