<?php

namespace App\Tests\Domain\Shared\Adapter;

use App\Domain\Shared\Domain\Contract\JobRepositoryInterface;
use App\Domain\Shared\Domain\Exception\JobNotFoundException;
use App\Domain\Shared\Domain\Model\Job;
use App\Domain\Shared\Domain\Model\JobStatus;

class InMemoryJobRepository implements JobRepositoryInterface
{
    /** @var Job[] */
    private array $jobs = [];

    public function save(Job $job): void
    {
        $this->jobs[$job->id] = $job;
    }

    public function get(string $id): ?Job
    {
        return $this->jobs[$id] ?? null;
    }

    public function findByStatus(JobStatus $status): array
    {
        return array_filter($this->jobs, fn(Job $job) => $job->status === $status);
    }

    public function findByType(string $type): array
    {
        return array_filter($this->jobs, fn(Job $job) => $job->type === $type);
    }

    public function findPendingJobs(): array
    {
        return $this->findByStatus(JobStatus::PENDING);
    }

    public function findInProgressJobs(): array
    {
        return $this->findByStatus(JobStatus::IN_PROGRESS);
    }

    public function findFailedJobs(): array
    {
        return $this->findByStatus(JobStatus::FAILED);
    }

    public function findByCriteria(array $criteria): array
    {
        $jobs = $this->jobs;

        if (isset($criteria['statuses']) && is_array($criteria['statuses']) && !empty($criteria['statuses'])) {
            $jobs = array_filter($jobs, fn(Job $job) => in_array($job->status, $criteria['statuses']));
        } elseif (isset($criteria['status'])) {
            $jobs = array_filter($jobs, fn(Job $job) => $job->status === $criteria['status']);
        }

        if (isset($criteria['type'])) {
            $jobs = array_filter($jobs, fn(Job $job) => $job->type === $criteria['type']);
        }

        if (isset($criteria['createdAfter'])) {
            $jobs = array_filter($jobs, fn(Job $job) => $job->createdAt >= $criteria['createdAfter']);
        }

        if (isset($criteria['createdBefore'])) {
            $jobs = array_filter($jobs, fn(Job $job) => $job->createdAt <= $criteria['createdBefore']);
        }

        if (isset($criteria['sortBy'])) {
            usort($jobs, function(Job $a, Job $b) use ($criteria) {
                $sortOrder = $criteria['sortOrder'] ?? 'ASC';
                $comparison = match($criteria['sortBy']) {
                    'createdAt' => $a->createdAt <=> $b->createdAt,
                    'startedAt' => ($a->startedAt ?? new \DateTimeImmutable()) <=> ($b->startedAt ?? new \DateTimeImmutable()),
                    'completedAt' => ($a->completedAt ?? new \DateTimeImmutable()) <=> ($b->completedAt ?? new \DateTimeImmutable()),
                    default => 0
                };
                return $sortOrder === 'ASC' ? $comparison : -$comparison;
            });
        }

        if (isset($criteria['offset'])) {
            $jobs = array_slice($jobs, $criteria['offset']);
        }

        if (isset($criteria['limit'])) {
            $jobs = array_slice($jobs, 0, $criteria['limit']);
        }

        return array_values($jobs);
    }

    public function countByCriteria(array $criteria): int
    {
        return count($this->findByCriteria($criteria));
    }

    public function delete(string $id): void
    {
        if (!isset($this->jobs[$id])) {
            throw JobNotFoundException::withId($id);
        }

        unset($this->jobs[$id]);
    }

    public function deleteByCriteria(array $criteria): int
    {
        $toDelete = $this->findByCriteria($criteria);

        foreach ($toDelete as $job) {
            unset($this->jobs[$job->id]);
        }

        return count($toDelete);
    }

    public function clear(): void
    {
        $this->jobs = [];
    }
}
