<?php

declare(strict_types=1);

namespace App\Framework\Queue\ValueObjects;

use App\Framework\Core\ValueObjects\Timestamp;

/**
 * Value Object representing a batch of jobs for bulk processing
 */
final readonly class JobBatch
{
    public function __construct(
        public string $batchId,
        public string $name,
        public array $jobIds,
        public JobBatchStatus $status,
        public int $totalJobs,
        public int $processedJobs,
        public int $failedJobs,
        public ?Timestamp $createdAt = null,
        public ?Timestamp $startedAt = null,
        public ?Timestamp $completedAt = null,
        public ?Timestamp $failedAt = null,
        public array $options = []
    ) {
        $this->validate();
    }

    public static function create(
        string $batchId,
        string $name,
        array $jobIds,
        array $options = []
    ): self {
        return new self(
            batchId: $batchId,
            name: $name,
            jobIds: $jobIds,
            status: JobBatchStatus::PENDING,
            totalJobs: count($jobIds),
            processedJobs: 0,
            failedJobs: 0,
            createdAt: Timestamp::now(),
            options: $options
        );
    }

    public function start(): self
    {
        return new self(
            batchId: $this->batchId,
            name: $this->name,
            jobIds: $this->jobIds,
            status: JobBatchStatus::PROCESSING,
            totalJobs: $this->totalJobs,
            processedJobs: $this->processedJobs,
            failedJobs: $this->failedJobs,
            createdAt: $this->createdAt,
            startedAt: Timestamp::now(),
            completedAt: $this->completedAt,
            failedAt: $this->failedAt,
            options: $this->options
        );
    }

    public function incrementProcessed(): self
    {
        $newProcessed = $this->processedJobs + 1;
        $newStatus = $this->determineStatus($newProcessed, $this->failedJobs);

        return new self(
            batchId: $this->batchId,
            name: $this->name,
            jobIds: $this->jobIds,
            status: $newStatus,
            totalJobs: $this->totalJobs,
            processedJobs: $newProcessed,
            failedJobs: $this->failedJobs,
            createdAt: $this->createdAt,
            startedAt: $this->startedAt,
            completedAt: $newStatus === JobBatchStatus::COMPLETED ? Timestamp::now() : $this->completedAt,
            failedAt: $this->failedAt,
            options: $this->options
        );
    }

    public function incrementFailed(): self
    {
        $newFailed = $this->failedJobs + 1;
        $newStatus = $this->determineStatus($this->processedJobs, $newFailed);

        return new self(
            batchId: $this->batchId,
            name: $this->name,
            jobIds: $this->jobIds,
            status: $newStatus,
            totalJobs: $this->totalJobs,
            processedJobs: $this->processedJobs,
            failedJobs: $newFailed,
            createdAt: $this->createdAt,
            startedAt: $this->startedAt,
            completedAt: $this->completedAt,
            failedAt: $newStatus === JobBatchStatus::FAILED ? Timestamp::now() : $this->failedAt,
            options: $this->options
        );
    }

    public function getProgressPercentage(): float
    {
        if ($this->totalJobs === 0) {
            return 100.0;
        }

        return round(($this->processedJobs / $this->totalJobs) * 100, 2);
    }

    public function isCompleted(): bool
    {
        return $this->status === JobBatchStatus::COMPLETED;
    }

    public function isFailed(): bool
    {
        return $this->status === JobBatchStatus::FAILED;
    }

    public function isFinished(): bool
    {
        return $this->isCompleted() || $this->isFailed();
    }

    public function getRemainingJobs(): int
    {
        return $this->totalJobs - $this->processedJobs - $this->failedJobs;
    }

    public function toArray(): array
    {
        return [
            'batch_id' => $this->batchId,
            'name' => $this->name,
            'job_ids' => $this->jobIds,
            'status' => $this->status->value,
            'total_jobs' => $this->totalJobs,
            'processed_jobs' => $this->processedJobs,
            'failed_jobs' => $this->failedJobs,
            'created_at' => $this->createdAt?->toRfc3339(),
            'started_at' => $this->startedAt?->toRfc3339(),
            'completed_at' => $this->completedAt?->toRfc3339(),
            'failed_at' => $this->failedAt?->toRfc3339(),
            'options' => $this->options,
        ];
    }

    private function determineStatus(int $processed, int $failed): JobBatchStatus
    {
        $completed = $processed + $failed;

        if ($completed === $this->totalJobs) {
            return $failed > 0 ? JobBatchStatus::FAILED : JobBatchStatus::COMPLETED;
        }

        return $this->status === JobBatchStatus::PENDING ? JobBatchStatus::PENDING : JobBatchStatus::PROCESSING;
    }

    private function validate(): void
    {
        if (empty($this->batchId)) {
            throw new \InvalidArgumentException('Batch ID cannot be empty');
        }

        if (empty($this->name)) {
            throw new \InvalidArgumentException('Batch name cannot be empty');
        }

        if ($this->totalJobs < 0) {
            throw new \InvalidArgumentException('Total jobs cannot be negative');
        }

        if ($this->processedJobs < 0) {
            throw new \InvalidArgumentException('Processed jobs cannot be negative');
        }

        if ($this->failedJobs < 0) {
            throw new \InvalidArgumentException('Failed jobs cannot be negative');
        }

        if ($this->processedJobs + $this->failedJobs > $this->totalJobs) {
            throw new \InvalidArgumentException('Processed + failed jobs cannot exceed total jobs');
        }
    }
}
