<?php

declare(strict_types=1);

namespace App\Framework\Queue\ValueObjects;

use App\Framework\Core\ValueObjects\Duration;
use App\Framework\Retry\RetryStrategy;
use App\Framework\Retry\Strategies\ExponentialBackoffStrategy;

/**
 * Job Payload Value Object
 *
 * Container for a job with all its configuration and metadata
 */
final readonly class JobPayload
{
    public function __construct(
        public object $job,
        public QueuePriority $priority,
        public Duration $delay,
        public ?Duration $timeout = null,
        public ?RetryStrategy $retryStrategy = null,
        public ?JobMetadata $metadata = null
    ) {
    }

    public static function create(
        object $job,
        ?QueuePriority $priority = null,
        ?Duration $delay = null,
        ?Duration $timeout = null,
        ?RetryStrategy $retryStrategy = null,
        ?JobMetadata $metadata = null
    ): self {
        return new self(
            job: $job,
            priority: $priority ?? QueuePriority::normal(),
            delay: $delay ?? Duration::zero(),
            timeout: $timeout,
            retryStrategy: $retryStrategy,
            metadata: $metadata ?? JobMetadata::create(['job' => $job])
        );
    }

    public static function immediate(object $job): self
    {
        return self::create(
            job: $job,
            priority: QueuePriority::high(),
            delay: Duration::zero()
        );
    }

    public static function delayed(object $job, Duration $delay): self
    {
        return self::create(
            job: $job,
            delay: $delay
        );
    }

    public static function critical(object $job): self
    {
        return self::create(
            job: $job,
            priority: QueuePriority::critical(),
            delay: Duration::zero(),
            timeout: Duration::fromSeconds(30)
        );
    }

    public static function background(object $job): self
    {
        return self::create(
            job: $job,
            priority: QueuePriority::low(),
            timeout: Duration::fromMinutes(30),
            retryStrategy: new ExponentialBackoffStrategy(maxAttempts: 5)
        );
    }

    public function withPriority(QueuePriority $priority): self
    {
        return new self(
            job: $this->job,
            priority: $priority,
            delay: $this->delay,
            timeout: $this->timeout,
            retryStrategy: $this->retryStrategy,
            metadata: $this->metadata
        );
    }

    public function withDelay(Duration $delay): self
    {
        return new self(
            job: $this->job,
            priority: $this->priority,
            delay: $delay,
            timeout: $this->timeout,
            retryStrategy: $this->retryStrategy,
            metadata: $this->metadata
        );
    }

    public function withTimeout(Duration $timeout): self
    {
        return new self(
            job: $this->job,
            priority: $this->priority,
            delay: $this->delay,
            timeout: $timeout,
            retryStrategy: $this->retryStrategy,
            metadata: $this->metadata
        );
    }

    public function withRetryStrategy(RetryStrategy $strategy): self
    {
        return new self(
            job: $this->job,
            priority: $this->priority,
            delay: $this->delay,
            timeout: $this->timeout,
            retryStrategy: $strategy,
            metadata: $this->metadata
        );
    }

    public function withMetadata(JobMetadata $metadata): self
    {
        return new self(
            job: $this->job,
            priority: $this->priority,
            delay: $this->delay,
            timeout: $this->timeout,
            retryStrategy: $this->retryStrategy,
            metadata: $metadata
        );
    }

    public function isReady(): bool
    {
        return $this->delay->toSeconds() === 0;
    }

    public function isDelayed(): bool
    {
        return $this->delay->toSeconds() > 0;
    }

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

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

    /**
     * Calculate available time (when the job can be processed)
     */
    public function getAvailableTime(): int
    {
        if ($this->delay->toSeconds() === 0) {
            return time();
        }

        return time() + (int) $this->delay->toSeconds();
    }

    /**
     * Serialize for storage
     */
    public function serialize(): string
    {
        return serialize($this->job);
    }

    /**
     * Get job class name
     */
    public function getJobClass(): string
    {
        return get_class($this->job);
    }

    public function toArray(): array
    {
        return [
            'job_class' => $this->getJobClass(),
            'priority' => $this->priority->toString(),
            'priority_value' => $this->priority->value,
            'delay_seconds' => $this->delay->toSeconds(),
            'timeout_seconds' => $this->timeout?->toSeconds(),
            'has_retry_strategy' => $this->hasRetryStrategy(),
            'max_attempts' => $this->retryStrategy?->getMaxAttempts(),
            'available_at' => $this->getAvailableTime(),
            'metadata' => $this->metadata?->toArray(),
        ];
    }
}
