<?php

declare(strict_types=1);

namespace App\Framework\Queue\Services;

use App\Framework\Core\Events\EventDispatcherInterface;
use App\Framework\Logging\Logger;
use App\Framework\Queue\Contracts\JobChainManagerInterface;
use App\Framework\Queue\Contracts\JobDependencyManagerInterface;
use App\Framework\Queue\Contracts\QueueInterface;
use App\Framework\Queue\Events\JobChainCompletedEvent;
use App\Framework\Queue\Events\JobChainFailedEvent;
use App\Framework\Queue\Events\JobChainStartedEvent;
use App\Framework\Queue\Exceptions\ChainNotFoundException;
use App\Framework\Queue\Exceptions\CircularDependencyException;
use App\Framework\Queue\Exceptions\InvalidChainStateException;
use App\Framework\Queue\ValueObjects\ChainExecutionMode;
use App\Framework\Queue\ValueObjects\Job;
use App\Framework\Queue\ValueObjects\JobChain;

final readonly class JobChainExecutionCoordinator
{
    public function __construct(
        private JobChainManagerInterface $chainManager,
        private JobDependencyManagerInterface $dependencyManager,
        private QueueInterface $queue,
        private EventDispatcherInterface $eventDispatcher,
        private DependencyResolutionEngine $resolutionEngine,
        private Logger $logger
    ) {
    }

    /**
     * Start execution of a job chain
     */
    public function startChainExecution(string $chainId): void
    {
        $chainEntry = $this->chainManager->getChain($chainId);

        if (! $chainEntry) {
            throw ChainNotFoundException::byId($chainId);
        }

        if (! $chainEntry->isPending()) {
            throw InvalidChainStateException::notPending($chainId, $chainEntry->status);
        }

        $jobChain = $chainEntry->getJobChain();

        $this->logger->info('Starting chain execution', [
            'chain_id' => $chainId,
            'execution_mode' => $jobChain->executionMode->value,
            'job_count' => count($jobChain->jobIds),
            'stop_on_failure' => $jobChain->stopOnFailure,
        ]);

        // Validate dependency graph before starting
        $validationResults = $this->resolutionEngine->validateDependencyGraph($jobChain->jobIds);
        $hasCircularDeps = array_filter($validationResults, fn ($result) => ! $result['valid']);

        if (! empty($hasCircularDeps)) {
            $this->logger->error('Cannot start chain - circular dependencies detected', [
                'chain_id' => $chainId,
                'circular_dependencies' => $hasCircularDeps,
            ]);

            throw CircularDependencyException::inChain($chainId, $hasCircularDeps);
        }

        // Mark chain as started
        $this->chainManager->startChain($chainId);

        // Dispatch chain started event
        $this->eventDispatcher->dispatch(new JobChainStartedEvent(
            chainId: $chainId,
            name: $jobChain->name,
            executionMode: $jobChain->executionMode,
            jobIds: $jobChain->jobIds
        ));

        // Start execution based on execution mode
        match($jobChain->executionMode) {
            ChainExecutionMode::SEQUENTIAL => $this->startSequentialExecution($jobChain),
            ChainExecutionMode::PARALLEL => $this->startParallelExecution($jobChain),
            ChainExecutionMode::CONDITIONAL => $this->startConditionalExecution($jobChain)
        };
    }

    /**
     * Process job completion within a chain context
     */
    public function handleJobCompletionInChain(string $jobId, bool $successful = true, ?array $jobResult = null): void
    {
        $this->logger->info('Handling job completion in chain context', [
            'job_id' => $jobId,
            'successful' => $successful,
        ]);

        // Get all chains this job belongs to
        $chains = $this->chainManager->getChainsForJob($jobId);

        foreach ($chains as $chainEntry) {
            if (! $chainEntry->isRunning()) {
                continue;
            }

            $jobChain = $chainEntry->getJobChain();

            $this->logger->info('Processing job completion for chain', [
                'chain_id' => $chainEntry->chainId,
                'job_id' => $jobId,
                'execution_mode' => $jobChain->executionMode->value,
            ]);

            // Handle failure scenarios
            if (! $successful && $jobChain->stopOnFailure) {
                $this->handleChainFailure($chainEntry->chainId, $jobId);

                continue;
            }

            // Continue chain execution based on mode
            match($jobChain->executionMode) {
                ChainExecutionMode::SEQUENTIAL => $this->continueSequentialExecution($chainEntry, $jobId, $successful),
                ChainExecutionMode::PARALLEL => $this->continueParallelExecution($chainEntry, $jobId, $successful),
                ChainExecutionMode::CONDITIONAL => $this->continueConditionalExecution($chainEntry, $jobId, $successful, $jobResult)
            };
        }
    }

    /**
     * Get chain execution status and progress
     */
    public function getChainExecutionStatus(string $chainId): array
    {
        $chainEntry = $this->chainManager->getChain($chainId);

        if (! $chainEntry) {
            throw ChainNotFoundException::byId($chainId);
        }

        $progress = $this->chainManager->getChainProgress($chainId);
        $jobChain = $chainEntry->getJobChain();

        // Get detailed job status for each job in chain
        $jobStatuses = [];
        foreach ($jobChain->jobIds as $index => $jobId) {
            $dependencyAnalysis = $this->resolutionEngine->analyzeDependencies($jobId);

            $jobStatuses[] = [
                'job_id' => $jobId,
                'position' => $index,
                'can_execute' => $dependencyAnalysis['can_execute'],
                'dependencies_satisfied' => $dependencyAnalysis['statistics']['satisfied_dependencies'],
                'dependencies_total' => $dependencyAnalysis['statistics']['total_dependencies'],
                'status' => $this->getJobStatus($jobId), // Would need integration with job status tracking
            ];
        }

        return [
            'chain_id' => $chainId,
            'name' => $chainEntry->name,
            'status' => $chainEntry->status,
            'execution_mode' => $jobChain->executionMode->value,
            'progress' => $progress,
            'job_statuses' => $jobStatuses,
            'started_at' => $chainEntry->startedAt,
            'completed_at' => $chainEntry->completedAt,
            'metadata' => $chainEntry->getMetadataArray(),
        ];
    }

    /**
     * Pause chain execution
     */
    public function pauseChainExecution(string $chainId): void
    {
        // Implementation would depend on job queue capabilities
        $this->logger->info('Pausing chain execution', ['chain_id' => $chainId]);

        // This would involve:
        // 1. Marking chain as paused
        // 2. Preventing new jobs from being queued
        // 3. Allowing current jobs to complete
    }

    /**
     * Resume chain execution
     */
    public function resumeChainExecution(string $chainId): void
    {
        $this->logger->info('Resuming chain execution', ['chain_id' => $chainId]);

        // This would involve:
        // 1. Marking chain as running again
        // 2. Re-evaluating which jobs can be executed
        // 3. Queuing eligible jobs
    }

    private function startSequentialExecution(JobChain $jobChain): void
    {
        $this->logger->info('Starting sequential chain execution', [
            'chain_id' => $jobChain->chainId,
        ]);

        // Queue the first job that has no unsatisfied dependencies
        foreach ($jobChain->jobIds as $jobId) {
            if ($this->dependencyManager->canJobBeExecuted($jobId)) {
                $this->queueJob($jobId);

                break; // Only start first eligible job in sequential mode
            }
        }
    }

    private function startParallelExecution(JobChain $jobChain): void
    {
        $this->logger->info('Starting parallel chain execution', [
            'chain_id' => $jobChain->chainId,
        ]);

        // Queue all jobs that have no unsatisfied dependencies
        foreach ($jobChain->jobIds as $jobId) {
            if ($this->dependencyManager->canJobBeExecuted($jobId)) {
                $this->queueJob($jobId);
            }
        }
    }

    private function startConditionalExecution(JobChain $jobChain): void
    {
        $this->logger->info('Starting conditional chain execution', [
            'chain_id' => $jobChain->chainId,
        ]);

        // Similar to parallel, but with conditional dependency evaluation
        foreach ($jobChain->jobIds as $jobId) {
            if ($this->dependencyManager->canJobBeExecuted($jobId)) {
                $this->queueJob($jobId);
            }
        }
    }

    private function continueSequentialExecution(object $chainEntry, string $completedJobId, bool $successful): void
    {
        $nextJobId = $this->chainManager->getNextJobInChain($chainEntry->chainId, $completedJobId);

        if ($nextJobId !== null && $this->dependencyManager->canJobBeExecuted($nextJobId)) {
            $this->queueJob($nextJobId);
        } elseif ($nextJobId === null) {
            // Chain completed
            $this->handleChainCompletion($chainEntry->chainId);
        }
    }

    private function continueParallelExecution(object $chainEntry, string $completedJobId, bool $successful): void
    {
        $jobChain = $chainEntry->getJobChain();

        // Check if any more jobs can be executed
        $hasExecutableJobs = false;
        foreach ($jobChain->jobIds as $jobId) {
            if ($this->dependencyManager->canJobBeExecuted($jobId) && ! $this->isJobCompleted($jobId)) {
                $this->queueJob($jobId);
                $hasExecutableJobs = true;
            }
        }

        // Check if chain is complete
        if (! $hasExecutableJobs && $this->areAllJobsCompleted($jobChain->jobIds)) {
            $this->handleChainCompletion($chainEntry->chainId);
        }
    }

    private function continueConditionalExecution(object $chainEntry, string $completedJobId, bool $successful, ?array $jobResult): void
    {
        // Conditional execution logic would evaluate conditions based on job results
        $jobChain = $chainEntry->getJobChain();

        // Re-evaluate all dependencies in case conditions have changed
        foreach ($jobChain->jobIds as $jobId) {
            if ($this->dependencyManager->canJobBeExecuted($jobId) && ! $this->isJobCompleted($jobId)) {
                $this->queueJob($jobId);
            }
        }

        // Check completion
        if ($this->areAllJobsCompleted($jobChain->jobIds)) {
            $this->handleChainCompletion($chainEntry->chainId);
        }
    }

    private function handleChainCompletion(string $chainId): void
    {
        $this->logger->info('Chain execution completed', ['chain_id' => $chainId]);

        $this->chainManager->markChainAsCompleted($chainId);

        $chainEntry = $this->chainManager->getChain($chainId);
        $this->eventDispatcher->dispatch(new JobChainCompletedEvent(
            chainId: $chainId,
            name: $chainEntry->name,
            completedAt: $chainEntry->completedAt
        ));
    }

    private function handleChainFailure(string $chainId, string $failedJobId): void
    {
        $this->logger->warning('Chain execution failed', [
            'chain_id' => $chainId,
            'failed_job_id' => $failedJobId,
        ]);

        $this->chainManager->markChainAsFailed($chainId);

        $chainEntry = $this->chainManager->getChain($chainId);
        $this->eventDispatcher->dispatch(new JobChainFailedEvent(
            chainId: $chainId,
            name: $chainEntry->name,
            failedJobId: $failedJobId,
            failedAt: $chainEntry->completedAt
        ));
    }

    private function queueJob(string $jobId): void
    {
        $this->logger->debug('Queuing job for execution', ['job_id' => $jobId]);

        // This would need integration with the actual job creation/queuing system
        // For now, we'll assume a job can be queued by ID
        // In a real implementation, you'd need to reconstruct the Job object
        // $job = $this->jobRepository->find($jobId);
        // $this->queue->push($job);
    }

    private function getJobStatus(string $jobId): string
    {
        // This would integrate with job status tracking system
        // For now, return a placeholder
        return 'unknown';
    }

    private function isJobCompleted(string $jobId): bool
    {
        // This would check if job is completed in the job tracking system
        // For now, return false as placeholder
        return false;
    }

    private function areAllJobsCompleted(array $jobIds): bool
    {
        foreach ($jobIds as $jobId) {
            if (! $this->isJobCompleted($jobId)) {
                return false;
            }
        }

        return true;
    }
}
