<?php

declare(strict_types=1);

namespace App\Framework\Queue\ValueObjects;

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

/**
 * Job state representation for state management
 */
final readonly class JobState implements SerializableState
{
    public function __construct(
        public JobId $jobId,
        public JobStatus $status,
        public QueueType $queueType,
        public QueuePriority $priority,
        public Timestamp $createdAt,
        public ?Timestamp $startedAt = null,
        public ?Timestamp $completedAt = null,
        public ?string $errorMessage = null,
        public int $attempts = 0,
        public int $maxAttempts = 3,
        public array $metadata = []
    ) {
    }

    /**
     * Create initial job state
     */
    public static function create(
        JobId $jobId,
        QueueType $queueType,
        QueuePriority $priority,
        int $maxAttempts = 3,
        array $metadata = []
    ): self {
        return new self(
            jobId: $jobId,
            status: JobStatus::PENDING,
            queueType: $queueType,
            priority: $priority,
            createdAt: Timestamp::now(),
            maxAttempts: $maxAttempts,
            metadata: $metadata
        );
    }

    /**
     * Mark job as processing
     */
    public function markAsProcessing(): self
    {
        if (! $this->status->canTransitionTo(JobStatus::PROCESSING)) {
            throw new \InvalidArgumentException(
                "Cannot transition from {$this->status->value} to processing"
            );
        }

        return new self(
            jobId: $this->jobId,
            status: JobStatus::PROCESSING,
            queueType: $this->queueType,
            priority: $this->priority,
            createdAt: $this->createdAt,
            startedAt: Timestamp::now(),
            completedAt: $this->completedAt,
            errorMessage: $this->errorMessage,
            attempts: $this->attempts + 1,
            maxAttempts: $this->maxAttempts,
            metadata: $this->metadata
        );
    }

    /**
     * Mark job as completed
     */
    public function markAsCompleted(): self
    {
        if (! $this->status->canTransitionTo(JobStatus::COMPLETED)) {
            throw new \InvalidArgumentException(
                "Cannot transition from {$this->status->value} to completed"
            );
        }

        return new self(
            jobId: $this->jobId,
            status: JobStatus::COMPLETED,
            queueType: $this->queueType,
            priority: $this->priority,
            createdAt: $this->createdAt,
            startedAt: $this->startedAt,
            completedAt: Timestamp::now(),
            errorMessage: $this->errorMessage,
            attempts: $this->attempts,
            maxAttempts: $this->maxAttempts,
            metadata: $this->metadata
        );
    }

    /**
     * Mark job as failed
     */
    public function markAsFailed(string $errorMessage): self
    {
        if (! $this->status->canTransitionTo(JobStatus::FAILED)) {
            throw new \InvalidArgumentException(
                "Cannot transition from {$this->status->value} to failed"
            );
        }

        return new self(
            jobId: $this->jobId,
            status: JobStatus::FAILED,
            queueType: $this->queueType,
            priority: $this->priority,
            createdAt: $this->createdAt,
            startedAt: $this->startedAt,
            completedAt: Timestamp::now(),
            errorMessage: $errorMessage,
            attempts: $this->attempts,
            maxAttempts: $this->maxAttempts,
            metadata: $this->metadata
        );
    }

    /**
     * Mark job for retry
     */
    public function markForRetry(string $errorMessage): self
    {
        if ($this->attempts >= $this->maxAttempts) {
            return $this->markAsFailed("Max attempts exceeded: " . $errorMessage);
        }

        if (! $this->status->canTransitionTo(JobStatus::RETRYING)) {
            throw new \InvalidArgumentException(
                "Cannot transition from {$this->status->value} to retrying"
            );
        }

        return new self(
            jobId: $this->jobId,
            status: JobStatus::RETRYING,
            queueType: $this->queueType,
            priority: $this->priority,
            createdAt: $this->createdAt,
            startedAt: $this->startedAt,
            completedAt: $this->completedAt,
            errorMessage: $errorMessage,
            attempts: $this->attempts,
            maxAttempts: $this->maxAttempts,
            metadata: $this->metadata
        );
    }

    /**
     * Add metadata
     */
    public function withMetadata(array $metadata): self
    {
        return new self(
            jobId: $this->jobId,
            status: $this->status,
            queueType: $this->queueType,
            priority: $this->priority,
            createdAt: $this->createdAt,
            startedAt: $this->startedAt,
            completedAt: $this->completedAt,
            errorMessage: $this->errorMessage,
            attempts: $this->attempts,
            maxAttempts: $this->maxAttempts,
            metadata: array_merge($this->metadata, $metadata)
        );
    }

    /**
     * Get processing duration
     */
    public function getProcessingDuration(): ?Duration
    {
        if ($this->startedAt === null || $this->completedAt === null) {
            return null;
        }

        return Duration::fromSeconds(
            $this->completedAt->toFloat() - $this->startedAt->toFloat()
        );
    }

    /**
     * Check if job can be retried
     */
    public function canRetry(): bool
    {
        return $this->attempts < $this->maxAttempts &&
               $this->status->canTransitionTo(JobStatus::RETRYING);
    }

    /**
     * {@inheritdoc}
     */
    public function toArray(): array
    {
        return [
            'job_id' => $this->jobId->toString(),
            'status' => $this->status->value,
            'queue_type' => $this->queueType->value,
            'priority' => $this->priority->value,
            'created_at' => $this->createdAt->toFloat(),
            'started_at' => $this->startedAt?->toFloat(),
            'completed_at' => $this->completedAt?->toFloat(),
            'error_message' => $this->errorMessage,
            'attempts' => $this->attempts,
            'max_attempts' => $this->maxAttempts,
            'metadata' => $this->metadata,
        ];
    }

    /**
     * {@inheritdoc}
     */
    public static function fromArray(array $data): static
    {
        return new self(
            jobId: JobId::fromString($data['job_id']),
            status: JobStatus::from($data['status']),
            queueType: QueueType::from($data['queue_type']),
            priority: new QueuePriority($data['priority']),
            createdAt: Timestamp::fromFloat($data['created_at']),
            startedAt: $data['started_at'] ? Timestamp::fromFloat($data['started_at']) : null,
            completedAt: $data['completed_at'] ? Timestamp::fromFloat($data['completed_at']) : null,
            errorMessage: $data['error_message'] ?? null,
            attempts: $data['attempts'],
            maxAttempts: $data['max_attempts'],
            metadata: $data['metadata'] ?? []
        );
    }
}
