<?php

declare(strict_types=1);

namespace App\Framework\Scheduler\ValueObjects;

use App\Framework\Core\ValueObjects\Duration;
use App\Framework\Core\ValueObjects\Timestamp;

/**
 * Value Object for single job scheduling (non-recurring)
 */
final readonly class JobSchedule
{
    public function __construct(
        public ?Timestamp $scheduledAt = null,
        public ?Timestamp $notBefore = null,
        public ?Timestamp $notAfter = null
    ) {
        $this->validate();
    }

    public static function immediate(): self
    {
        return new self();
    }

    public static function delayed(Duration $delay): self
    {
        return new self(
            scheduledAt: Timestamp::now()->add($delay)
        );
    }

    public static function at(Timestamp $scheduledAt): self
    {
        return new self(scheduledAt: $scheduledAt);
    }

    public static function between(Timestamp $notBefore, Timestamp $notAfter): self
    {
        return new self(
            notBefore: $notBefore,
            notAfter: $notAfter
        );
    }

    public static function atWithWindow(
        Timestamp $scheduledAt,
        Timestamp $notBefore,
        Timestamp $notAfter
    ): self {
        return new self(
            scheduledAt: $scheduledAt,
            notBefore: $notBefore,
            notAfter: $notAfter
        );
    }

    public function isImmediate(): bool
    {
        return $this->scheduledAt === null;
    }

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

    public function hasTimeWindow(): bool
    {
        return $this->notBefore !== null || $this->notAfter !== null;
    }

    public function shouldExecuteNow(): bool
    {
        $now = Timestamp::now();

        // Check time window constraints
        if ($this->notBefore && $now->isBefore($this->notBefore)) {
            return false;
        }

        if ($this->notAfter && $now->isAfter($this->notAfter)) {
            return false;
        }

        // Immediate execution
        if ($this->isImmediate()) {
            return true;
        }

        // Scheduled execution
        return $this->scheduledAt && $now->isAfterOrEqual($this->scheduledAt);
    }

    public function getExecutionTime(): ?Timestamp
    {
        return $this->scheduledAt;
    }

    public function withScheduledAt(Timestamp $scheduledAt): self
    {
        return new self(
            scheduledAt: $scheduledAt,
            notBefore: $this->notBefore,
            notAfter: $this->notAfter
        );
    }

    public function withTimeWindow(Timestamp $notBefore, Timestamp $notAfter): self
    {
        return new self(
            scheduledAt: $this->scheduledAt,
            notBefore: $notBefore,
            notAfter: $notAfter
        );
    }

    public function toArray(): array
    {
        return [
            'scheduled_at' => $this->scheduledAt?->toRfc3339(),
            'not_before' => $this->notBefore?->toRfc3339(),
            'not_after' => $this->notAfter?->toRfc3339(),
        ];
    }

    public static function fromArray(array $data): self
    {
        return new self(
            scheduledAt: isset($data['scheduled_at']) ? Timestamp::fromRfc3339($data['scheduled_at']) : null,
            notBefore: isset($data['not_before']) ? Timestamp::fromRfc3339($data['not_before']) : null,
            notAfter: isset($data['not_after']) ? Timestamp::fromRfc3339($data['not_after']) : null
        );
    }

    private function validate(): void
    {
        if ($this->notBefore && $this->notAfter && $this->notAfter->isBefore($this->notBefore)) {
            throw new \InvalidArgumentException('notAfter cannot be before notBefore');
        }

        if ($this->scheduledAt && $this->notBefore && $this->scheduledAt->isBefore($this->notBefore)) {
            throw new \InvalidArgumentException('scheduledAt cannot be before notBefore');
        }

        if ($this->scheduledAt && $this->notAfter && $this->scheduledAt->isAfter($this->notAfter)) {
            throw new \InvalidArgumentException('scheduledAt cannot be after notAfter');
        }
    }
}
