<?php

declare(strict_types=1);

namespace App\Framework\Queue\Services;

use App\Framework\Database\EntityManagerInterface;
use App\Framework\Database\ValueObjects\SqlQuery;
use App\Framework\Logging\Logger;
use App\Framework\Queue\Contracts\JobDependencyManagerInterface;
use App\Framework\Queue\Entities\JobDependencyEntry;
use App\Framework\Queue\ValueObjects\JobDependency;

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

    public function addDependency(JobDependency $dependency): void
    {
        $entry = JobDependencyEntry::fromJobDependency($dependency);

        $this->logger->info('Adding job dependency', [
            'dependent_job_id' => $dependency->dependentJobId,
            'depends_on_job_id' => $dependency->dependsOnJobId,
            'type' => $dependency->type->value,
        ]);

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

    public function removeDependency(string $dependentJobId, string $dependsOnJobId): void
    {
        $query = "DELETE FROM job_dependencies
                  WHERE dependent_job_id = ? AND depends_on_job_id = ?";

        $this->logger->info('Removing job dependency', [
            'dependent_job_id' => $dependentJobId,
            'depends_on_job_id' => $dependsOnJobId,
        ]);

        $this->entityManager->getConnection()->execute(
            SqlQuery::create($query, [$dependentJobId, $dependsOnJobId])
        );
    }

    public function getDependencies(string $jobId): array
    {
        $query = "SELECT * FROM job_dependencies WHERE dependent_job_id = ? ORDER BY created_at";
        $results = $this->entityManager->getConnection()->query(
            SqlQuery::create($query, [$jobId])
        )->fetchAll();

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

    public function getDependents(string $jobId): array
    {
        $query = "SELECT * FROM job_dependencies WHERE depends_on_job_id = ? ORDER BY created_at";
        $results = $this->entityManager->getConnection()->query(
            SqlQuery::create($query, [$jobId])
        )->fetchAll();

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

    public function hasUnsatisfiedDependencies(string $jobId): bool
    {
        $query = "SELECT COUNT(*) as count FROM job_dependencies
                  WHERE dependent_job_id = ? AND is_satisfied = false";

        $result = $this->entityManager->getConnection()->queryOne(
            SqlQuery::create($query, [$jobId])
        );

        return $result['count'] > 0;
    }

    public function getUnsatisfiedDependencies(string $jobId): array
    {
        $query = "SELECT * FROM job_dependencies
                  WHERE dependent_job_id = ? AND is_satisfied = false
                  ORDER BY created_at";

        $results = $this->entityManager->getConnection()->query(
            SqlQuery::create($query, [$jobId])
        )->fetchAll();

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

    public function markDependencyAsSatisfied(string $dependentJobId, string $dependsOnJobId): void
    {
        $query = "UPDATE job_dependencies
                  SET is_satisfied = true, satisfied_at = ?, updated_at = ?
                  WHERE dependent_job_id = ? AND depends_on_job_id = ?";

        $now = date('Y-m-d H:i:s');

        $this->logger->info('Marking dependency as satisfied', [
            'dependent_job_id' => $dependentJobId,
            'depends_on_job_id' => $dependsOnJobId,
        ]);

        $this->entityManager->getConnection()->execute(
            SqlQuery::create($query, [$now, $now, $dependentJobId, $dependsOnJobId])
        );
    }

    public function canJobBeExecuted(string $jobId): bool
    {
        return ! $this->hasUnsatisfiedDependencies($jobId);
    }

    public function getReadyJobs(): array
    {
        // Get all jobs that have dependencies but no unsatisfied ones
        $query = "SELECT DISTINCT jd1.dependent_job_id
                  FROM job_dependencies jd1
                  WHERE NOT EXISTS (
                      SELECT 1 FROM job_dependencies jd2
                      WHERE jd2.dependent_job_id = jd1.dependent_job_id
                      AND jd2.is_satisfied = false
                  )";

        $results = $this->entityManager->getConnection()->query(
            SqlQuery::create($query)
        )->fetchAll();

        return array_column($results, 'dependent_job_id');
    }

    public function resolveJobCompletion(string $jobId, bool $successful = true): array
    {
        $this->logger->info('Resolving job completion for dependencies', [
            'job_id' => $jobId,
            'successful' => $successful,
        ]);

        // Get all jobs that depend on this completed job
        $dependents = $this->getDependents($jobId);
        $resolvedJobs = [];

        foreach ($dependents as $dependencyEntry) {
            $dependency = $dependencyEntry->getJobDependency();

            // Check if this dependency should be satisfied based on completion type
            $shouldSatisfy = match($dependency->type) {
                \App\Framework\Queue\ValueObjects\DependencyType::COMPLETION => true,
                \App\Framework\Queue\ValueObjects\DependencyType::SUCCESS => $successful,
                \App\Framework\Queue\ValueObjects\DependencyType::CONDITIONAL => $this->evaluateCondition($dependency->condition, $successful)
            };

            if ($shouldSatisfy && ! $dependencyEntry->isSatisfied) {
                $this->markDependencyAsSatisfied($dependency->dependentJobId, $jobId);

                // Check if the dependent job is now ready to execute
                if ($this->canJobBeExecuted($dependency->dependentJobId)) {
                    $resolvedJobs[] = $dependency->dependentJobId;
                }
            }
        }

        $this->logger->info('Job completion resolved', [
            'job_id' => $jobId,
            'resolved_jobs_count' => count($resolvedJobs),
            'resolved_jobs' => $resolvedJobs,
        ]);

        return $resolvedJobs;
    }

    public function getDependencyChain(string $jobId): array
    {
        $visited = [];
        $chain = [];

        $this->buildDependencyChain($jobId, $visited, $chain);

        // Remove the starting job from the chain if it's there
        return array_filter($chain, fn ($id) => $id !== $jobId);
    }

    public function hasCircularDependencies(string $jobId): bool
    {
        $visited = [];
        $recursionStack = [];

        return $this->detectCircularDependency($jobId, $visited, $recursionStack);
    }

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

        $query = "DELETE FROM job_dependencies
                  WHERE is_satisfied = true AND satisfied_at < ?";

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

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

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

        return $affectedRows;
    }

    private function buildDependencyChain(string $jobId, array &$visited, array &$chain): void
    {
        if (in_array($jobId, $visited)) {
            return;
        }

        $visited[] = $jobId;
        $dependencies = $this->getDependencies($jobId);

        foreach ($dependencies as $dependencyEntry) {
            $dependency = $dependencyEntry->getJobDependency();
            $chain[] = $dependency->dependsOnJobId;
            $this->buildDependencyChain($dependency->dependsOnJobId, $visited, $chain);
        }
    }

    private function detectCircularDependency(string $jobId, array &$visited, array &$recursionStack): bool
    {
        $visited[$jobId] = true;
        $recursionStack[$jobId] = true;

        $dependencies = $this->getDependencies($jobId);

        foreach ($dependencies as $dependencyEntry) {
            $dependency = $dependencyEntry->getJobDependency();
            $dependsOnJobId = $dependency->dependsOnJobId;

            if (! isset($visited[$dependsOnJobId])) {
                if ($this->detectCircularDependency($dependsOnJobId, $visited, $recursionStack)) {
                    return true;
                }
            } elseif (isset($recursionStack[$dependsOnJobId])) {
                return true; // Circular dependency detected
            }
        }

        unset($recursionStack[$jobId]);

        return false;
    }

    private function evaluateCondition(?string $condition, bool $jobSuccessful): bool
    {
        if ($condition === null) {
            return true;
        }

        // Simple condition evaluation - can be extended for more complex conditions
        return match($condition) {
            'on_success' => $jobSuccessful,
            'on_failure' => ! $jobSuccessful,
            'always' => true,
            default => true
        };
    }

    private function mapRowToEntity(array $row): JobDependencyEntry
    {
        return new JobDependencyEntry(
            id: $row['id'],
            dependentJobId: $row['dependent_job_id'],
            dependsOnJobId: $row['depends_on_job_id'],
            dependencyType: $row['dependency_type'],
            createdAt: $row['created_at'],
            updatedAt: $row['updated_at'],
            conditionExpression: $row['condition_expression'],
            isSatisfied: (bool) $row['is_satisfied'],
            satisfiedAt: $row['satisfied_at']
        );
    }
}
