<?php

declare(strict_types=1);

namespace App\Framework\Queue\ValueObjects;

use App\Framework\Core\ValueObjects\ClassName;
use App\Framework\Core\ValueObjects\Duration;
use App\Framework\Core\ValueObjects\Timestamp;
use App\Framework\DateTime\SystemClock;
use App\Framework\Ulid\Ulid;

/**
 * Job Metadata Value Object
 *
 * Contains metadata about a queued job
 */
final readonly class JobMetadata
{
    public function __construct(
        public Ulid $id,
        public ClassName $class,
        public string $type,
        public Timestamp $queuedAt,
        public ?Timestamp $startedAt = null,
        public ?Timestamp $completedAt = null,
        public array $tags = [],
        public array $extra = []
    ) {
    }

    public static function create(array $data = []): self
    {
        $jobObject = $data['job'] ?? null;

        return new self(
            id: $data['id'] ?? new Ulid(new SystemClock()),
            class: $data['class'] ?? ($jobObject ? ClassName::fromObject($jobObject) : ClassName::fromString('unknown')),
            type: $data['type'] ?? 'job',
            queuedAt: $data['queued_at'] ?? Timestamp::now(),
            startedAt: $data['started_at'] ?? null,
            completedAt: $data['completed_at'] ?? null,
            tags: $data['tags'] ?? [],
            extra: $data['extra'] ?? []
        );
    }

    public static function forCommand(object $command): self
    {
        return self::create([
            'job' => $command,
            'type' => 'command',
            'tags' => ['command'],
        ]);
    }

    public static function forEvent(object $event): self
    {
        return self::create([
            'job' => $event,
            'type' => 'event',
            'tags' => ['event'],
        ]);
    }

    public static function forEmail(object $email): self
    {
        return self::create([
            'job' => $email,
            'type' => 'email',
            'tags' => ['email'],
        ]);
    }

    public function markStarted(): self
    {
        return new self(
            id: $this->id,
            class: $this->class,
            type: $this->type,
            queuedAt: $this->queuedAt,
            startedAt: Timestamp::now(),
            completedAt: $this->completedAt,
            tags: $this->tags,
            extra: $this->extra
        );
    }

    public function markCompleted(): self
    {
        return new self(
            id: $this->id,
            class: $this->class,
            type: $this->type,
            queuedAt: $this->queuedAt,
            startedAt: $this->startedAt,
            completedAt: Timestamp::now(),
            tags: $this->tags,
            extra: $this->extra
        );
    }

    public function withTag(string $tag): self
    {
        $tags = $this->tags;
        if (! in_array($tag, $tags, true)) {
            $tags[] = $tag;
        }

        return new self(
            id: $this->id,
            class: $this->class,
            type: $this->type,
            queuedAt: $this->queuedAt,
            startedAt: $this->startedAt,
            completedAt: $this->completedAt,
            tags: $tags,
            extra: $this->extra
        );
    }

    public function withExtra(string $key, mixed $value): self
    {
        $extra = $this->extra;
        $extra[$key] = $value;

        return new self(
            id: $this->id,
            class: $this->class,
            type: $this->type,
            queuedAt: $this->queuedAt,
            startedAt: $this->startedAt,
            completedAt: $this->completedAt,
            tags: $this->tags,
            extra: $extra
        );
    }

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

        return Duration::between($this->startedAt, $this->completedAt);
    }

    public function getWaitDuration(): ?Duration
    {
        if ($this->startedAt === null) {
            return null;
        }

        return Duration::between($this->queuedAt, $this->startedAt);
    }

    public function getTotalDuration(): ?Duration
    {
        if ($this->completedAt === null) {
            return null;
        }

        return Duration::between($this->queuedAt, $this->completedAt);
    }

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

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

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

    public function isProcessing(): bool
    {
        return $this->startedAt !== null && $this->completedAt === null;
    }

    public function hasTag(string $tag): bool
    {
        return in_array($tag, $this->tags, true);
    }

    public function toArray(): array
    {
        return [
            'id' => $this->id->toString(),
            'class' => $this->class->toString(),
            'type' => $this->type,
            'queued_at' => $this->queuedAt->toIso8601(),
            'started_at' => $this->startedAt?->toIso8601(),
            'completed_at' => $this->completedAt?->toIso8601(),
            'tags' => $this->tags,
            'extra' => $this->extra,
            'processing_duration' => $this->getProcessingDuration()?->toSeconds(),
            'wait_duration' => $this->getWaitDuration()?->toSeconds(),
            'total_duration' => $this->getTotalDuration()?->toSeconds(),
            'status' => match(true) {
                $this->isCompleted() => 'completed',
                $this->isProcessing() => 'processing',
                $this->isPending() => 'pending',
                default => 'unknown'
            },
        ];
    }
}
