<?php

declare(strict_types=1);

namespace App\Framework\Queue\Services;

use App\Framework\Database\EntityManagerInterface;
use App\Framework\Logging\Logger;
use App\Framework\Queue\Contracts\JobChainManagerInterface;
use App\Framework\Queue\Entities\JobChainEntry;
use App\Framework\Queue\Exceptions\ChainNotFoundException;
use App\Framework\Queue\Exceptions\InvalidChainStateException;
use App\Framework\Queue\ValueObjects\JobChain;

final readonly class DatabaseJobChainManager implements JobChainManagerInterface
{
    public function __construct(
        private EntityManagerInterface $entityManager,
        private Logger $logger
    ) {
    }

    public function createChain(JobChain $jobChain): void
    {
        $entry = JobChainEntry::fromJobChain($jobChain);

        $this->logger->info('Creating job chain', [
            'chain_id' => $jobChain->chainId,
            'name' => $jobChain->name,
            'job_count' => count($jobChain->jobIds),
            'execution_mode' => $jobChain->executionMode->value,
        ]);

        $this->entityManager->save($entry);
        $this->entityManager->flush();
    }

    public function getChain(string $chainId): ?JobChainEntry
    {
        $query = "SELECT * FROM job_chains WHERE chain_id = ?";
        $result = $this->entityManager->getConnection()->fetchOne($query, [$chainId]);

        if (! $result) {
            return null;
        }

        return $this->mapRowToEntity($result);
    }

    public function updateChain(JobChain $jobChain): void
    {
        $existing = $this->getChain($jobChain->chainId);

        if (! $existing) {
            throw ChainNotFoundException::byId($jobChain->chainId);
        }

        $updated = new JobChainEntry(
            id: $existing->id,
            chainId: $jobChain->chainId,
            name: $jobChain->name,
            jobIds: json_encode($jobChain->jobIds),
            executionMode: $jobChain->executionMode->value,
            createdAt: $existing->createdAt,
            updatedAt: date('Y-m-d H:i:s'),
            stopOnFailure: $jobChain->stopOnFailure,
            metadata: $jobChain->metadata ? json_encode($jobChain->metadata) : null,
            status: $existing->status,
            startedAt: $existing->startedAt,
            completedAt: $existing->completedAt
        );

        $this->logger->info('Updating job chain', [
            'chain_id' => $jobChain->chainId,
            'name' => $jobChain->name,
        ]);

        $this->entityManager->save($updated);
        $this->entityManager->flush();
    }

    public function deleteChain(string $chainId): void
    {
        $query = "DELETE FROM job_chains WHERE chain_id = ?";

        $this->logger->info('Deleting job chain', [
            'chain_id' => $chainId,
        ]);

        $this->entityManager->getConnection()->execute($query, [$chainId]);
    }

    public function startChain(string $chainId): void
    {
        $chain = $this->getChain($chainId);

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

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

        $started = $chain->markAsStarted();

        $this->logger->info('Starting job chain', [
            'chain_id' => $chainId,
            'job_count' => count($started->getJobIdsArray()),
        ]);

        $this->entityManager->save($started);
        $this->entityManager->flush();
    }

    public function getChainsForJob(string $jobId): array
    {
        $query = "SELECT * FROM job_chains WHERE JSON_CONTAINS(job_ids, ?)";
        $results = $this->entityManager->getConnection()->fetchAll($query, [json_encode($jobId)]);

        return array_map(function (array $row) {
            return $this->mapRowToEntity($row);
        }, $results);
    }

    public function getActiveChains(): array
    {
        $query = "SELECT * FROM job_chains WHERE status = 'running' ORDER BY started_at";
        $results = $this->entityManager->getConnection()->fetchAll($query);

        return array_map(function (array $row) {
            return $this->mapRowToEntity($row);
        }, $results);
    }

    public function getPendingChains(): array
    {
        $query = "SELECT * FROM job_chains WHERE status = 'pending' ORDER BY created_at";
        $results = $this->entityManager->getConnection()->fetchAll($query);

        return array_map(function (array $row) {
            return $this->mapRowToEntity($row);
        }, $results);
    }

    public function markChainAsCompleted(string $chainId): void
    {
        $chain = $this->getChain($chainId);

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

        $completed = $chain->markAsCompleted();

        $this->logger->info('Marking chain as completed', [
            'chain_id' => $chainId,
        ]);

        $this->entityManager->save($completed);
        $this->entityManager->flush();
    }

    public function markChainAsFailed(string $chainId): void
    {
        $chain = $this->getChain($chainId);

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

        $failed = $chain->markAsFailed();

        $this->logger->info('Marking chain as failed', [
            'chain_id' => $chainId,
        ]);

        $this->entityManager->save($failed);
        $this->entityManager->flush();
    }

    public function getNextJobInChain(string $chainId, ?string $currentJobId = null): ?string
    {
        $chain = $this->getChain($chainId);

        if (! $chain) {
            return null;
        }

        $jobChain = $chain->getJobChain();

        if ($currentJobId === null) {
            // Return first job in chain
            return $jobChain->jobIds[0] ?? null;
        }

        return $jobChain->getNextJob($currentJobId);
    }

    public function isChainComplete(string $chainId): bool
    {
        $chain = $this->getChain($chainId);

        if (! $chain) {
            return false;
        }

        return $chain->isCompleted() || $chain->isFailed();
    }

    public function handleJobCompletion(string $jobId, bool $successful = true): void
    {
        $chains = $this->getChainsForJob($jobId);

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

            $jobChain = $chainEntry->getJobChain();

            // If chain requires stopping on failure and job failed
            if (! $successful && $jobChain->stopOnFailure) {
                $this->markChainAsFailed($chainEntry->chainId);
                $this->logger->warning('Chain failed due to job failure', [
                    'chain_id' => $chainEntry->chainId,
                    'failed_job_id' => $jobId,
                ]);

                continue;
            }

            // Check if this was the last job in the chain
            $nextJobId = $this->getNextJobInChain($chainEntry->chainId, $jobId);

            if ($nextJobId === null) {
                // Chain is complete
                $this->markChainAsCompleted($chainEntry->chainId);
                $this->logger->info('Chain completed', [
                    'chain_id' => $chainEntry->chainId,
                    'last_job_id' => $jobId,
                ]);
            } else {
                $this->logger->info('Job completed in chain, next job available', [
                    'chain_id' => $chainEntry->chainId,
                    'completed_job_id' => $jobId,
                    'next_job_id' => $nextJobId,
                ]);
            }
        }
    }

    public function getChainProgress(string $chainId): array
    {
        $chain = $this->getChain($chainId);

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

        $jobChain = $chain->getJobChain();
        $totalJobs = count($jobChain->jobIds);

        // This would need integration with job status tracking
        // For now, return basic progress based on chain status
        $progress = match($chain->status) {
            'pending' => [
                'completed_jobs' => 0,
                'total_jobs' => $totalJobs,
                'percentage' => 0,
                'status' => 'pending',
            ],
            'running' => [
                'completed_jobs' => 0, // Would need actual job status check
                'total_jobs' => $totalJobs,
                'percentage' => 0, // Would calculate based on completed jobs
                'status' => 'running',
            ],
            'completed' => [
                'completed_jobs' => $totalJobs,
                'total_jobs' => $totalJobs,
                'percentage' => 100,
                'status' => 'completed',
            ],
            'failed' => [
                'completed_jobs' => 0, // Would need actual job status check
                'total_jobs' => $totalJobs,
                'percentage' => 0, // Partial progress before failure
                'status' => 'failed',
            ]
        };

        return array_merge($progress, [
            'chain_id' => $chainId,
            'name' => $chain->name,
            'execution_mode' => $chain->executionMode,
            'stop_on_failure' => $chain->stopOnFailure,
            'started_at' => $chain->startedAt,
            'completed_at' => $chain->completedAt,
        ]);
    }

    public function cleanupOldChains(int $olderThanDays = 30): int
    {
        $cutoffDate = date('Y-m-d H:i:s', strtotime("-{$olderThanDays} days"));

        $query = "DELETE FROM job_chains
                  WHERE status IN ('completed', 'failed') AND completed_at < ?";

        $this->logger->info('Cleaning up old chains', [
            'cutoff_date' => $cutoffDate,
            'older_than_days' => $olderThanDays,
        ]);

        $affectedRows = $this->entityManager->getConnection()->execute($query, [$cutoffDate]);

        $this->logger->info('Old chains cleaned up', [
            'deleted_count' => $affectedRows,
        ]);

        return $affectedRows;
    }

    private function mapRowToEntity(array $row): JobChainEntry
    {
        return new JobChainEntry(
            id: $row['id'],
            chainId: $row['chain_id'],
            name: $row['name'],
            jobIds: $row['job_ids'],
            executionMode: $row['execution_mode'],
            createdAt: $row['created_at'],
            updatedAt: $row['updated_at'],
            stopOnFailure: (bool) $row['stop_on_failure'],
            metadata: $row['metadata'],
            status: $row['status'],
            startedAt: $row['started_at'],
            completedAt: $row['completed_at']
        );
    }
}
