<?php
declare(strict_types=1);

namespace PpPhp\Core\Platform\Event\Scheduler;

use PpPhp\Core\Platform\Event\Contracts\EventInterface;
use PpPhp\Core\Platform\Event\Queue\DeferredEvent;

final class ScheduledEvent
{
    public function __construct(
        private readonly string $id,
        private readonly string $eventName,
        private readonly array $payload = [],
        private readonly array $metadata = [],
        private readonly string $expression = '',
        private readonly string $runAt = '',
        private readonly string $state = ScheduleState::PENDING,
        private readonly int $attempts = 0,
        private readonly int $maxAttempts = 1,
        private readonly bool $recurring = false,
        private readonly string $createdAt = ''
    ) {}

    public static function fromEvent(EventInterface|string $event, ScheduleExpression $expression, SchedulerClock $clock, array $payload = [], array $metadata = []): self
    {
        if ($event instanceof EventInterface) {
            return new self(
                ScheduleIdentifier::next(),
                $event->name(),
                $event->payload(),
                $event->metadata() + $metadata,
                $expression->value(),
                $expression->nextRunAt($clock),
                ScheduleState::PENDING,
                0,
                (int) ($metadata['maxAttempts'] ?? 1),
                str_starts_with($expression->value(), 'every:')
            );
        }

        return new self(
            ScheduleIdentifier::next(),
            $event,
            $payload,
            $metadata,
            $expression->value(),
            $expression->nextRunAt($clock),
            ScheduleState::PENDING,
            0,
            (int) ($metadata['maxAttempts'] ?? 1),
            str_starts_with($expression->value(), 'every:')
        );
    }

    public static function fromArray(array $data): self
    {
        return new self(
            (string) ($data['id'] ?? ScheduleIdentifier::next()),
            (string) ($data['eventName'] ?? ''),
            (array) ($data['payload'] ?? []),
            (array) ($data['metadata'] ?? []),
            (string) ($data['expression'] ?? ''),
            (string) ($data['runAt'] ?? ''),
            (string) ($data['state'] ?? ScheduleState::PENDING),
            (int) ($data['attempts'] ?? 0),
            (int) ($data['maxAttempts'] ?? 1),
            (bool) ($data['recurring'] ?? false),
            (string) ($data['createdAt'] ?? '')
        );
    }

    public function id(): string { return $this->id; }
    public function eventName(): string { return $this->eventName; }
    public function payload(): array { return $this->payload; }
    public function metadata(): array { return $this->metadata; }
    public function expression(): string { return $this->expression; }
    public function runAt(): string { return $this->runAt; }
    public function state(): string { return $this->state; }
    public function attempts(): int { return $this->attempts; }
    public function maxAttempts(): int { return $this->maxAttempts; }
    public function recurring(): bool { return $this->recurring; }
    public function createdAt(): string { return $this->createdAt !== '' ? $this->createdAt : date('c'); }

    public function isDue(?string $now = null): bool
    {
        return strtotime($this->runAt) <= strtotime($now ?? date('c'));
    }

    public function toDeferredEvent(): DeferredEvent
    {
        return DeferredEvent::fromEvent($this->eventName, $this->payload, $this->metadata + ['scheduleId' => $this->id]);
    }

    public function withState(string $state): self
    {
        return new self($this->id, $this->eventName, $this->payload, $this->metadata, $this->expression, $this->runAt, $state, $this->attempts, $this->maxAttempts, $this->recurring, $this->createdAt());
    }

    public function incrementAttempts(): self
    {
        return new self($this->id, $this->eventName, $this->payload, $this->metadata, $this->expression, $this->runAt, $this->state, $this->attempts + 1, $this->maxAttempts, $this->recurring, $this->createdAt());
    }

    public function reschedule(SchedulerClock $clock): self
    {
        $expression = new ScheduleExpression($this->expression);
        return new self($this->id, $this->eventName, $this->payload, $this->metadata, $this->expression, $expression->nextRunAt($clock), ScheduleState::PENDING, $this->attempts, $this->maxAttempts, $this->recurring, $this->createdAt());
    }

    public function toArray(): array
    {
        return [
            'id' => $this->id,
            'eventName' => $this->eventName,
            'payload' => $this->payload,
            'metadata' => $this->metadata,
            'expression' => $this->expression,
            'runAt' => $this->runAt,
            'state' => $this->state,
            'attempts' => $this->attempts,
            'maxAttempts' => $this->maxAttempts,
            'recurring' => $this->recurring,
            'createdAt' => $this->createdAt(),
        ];
    }
}
