<?php

declare(strict_types=1);

namespace App\Framework\Queue\Services;

use App\Framework\Core\ValueObjects\Percentage;
use App\Framework\Database\ConnectionInterface;
use App\Framework\Database\EntityManager;
use App\Framework\Database\ValueObjects\SqlQuery;
use App\Framework\Queue\Contracts\JobProgressTrackerInterface;
use App\Framework\Queue\Entities\JobProgressEntry;
use App\Framework\Queue\ValueObjects\JobProgress;
use App\Framework\Queue\ValueObjects\ProgressStep;

/**
 * Database-backed job progress tracker implementation
 */
final readonly class DatabaseJobProgressTracker implements JobProgressTrackerInterface
{
    public function __construct(
        private ConnectionInterface $connection,
        private EntityManager $entityManager
    ) {
    }

    public function updateProgress(string $jobId, JobProgress $progress, ?string $stepName = null): void
    {
        $progressEntry = JobProgressEntry::fromJobProgress($jobId, $progress, $stepName);
        $this->entityManager->persist($progressEntry);
        $this->entityManager->flush();
    }

    public function completeStep(string $jobId, ProgressStep $step): void
    {
        $completedStep = $step->markAsCompleted();

        // Create progress entry for the completed step
        $progress = JobProgress::withPercentage(
            percentage: Percentage::zero(), // Will be updated based on overall progress
            message: "Completed step: {$completedStep->description}",
            metadata: $completedStep->metadata
        );

        $this->updateProgress($jobId, $progress, $completedStep->stepName);
    }

    public function getCurrentProgress(string $jobId): ?JobProgress
    {
        $latestEntry = $this->getLatestProgressEntry($jobId);

        return $latestEntry?->getJobProgress();
    }

    public function getProgressHistory(string $jobId): array
    {
        $sql = "
            SELECT * FROM job_progress
            WHERE job_id = ?
            ORDER BY updated_at ASC
        ";

        $result = $this->connection->query(SqlQuery::create($sql, [$jobId]));
        $rows = $result->fetchAll();

        return array_map([$this, 'mapRowToProgressEntry'], $rows);
    }

    public function getLatestProgressEntry(string $jobId): ?JobProgressEntry
    {
        $sql = "
            SELECT * FROM job_progress
            WHERE job_id = ?
            ORDER BY updated_at DESC
            LIMIT 1
        ";

        $row = $this->connection->queryOne(SqlQuery::create($sql, [$jobId]));

        return $row ? $this->mapRowToProgressEntry($row) : null;
    }

    public function markJobCompleted(string $jobId, string $message = 'Job completed successfully'): void
    {
        $progress = JobProgress::completed($message);
        $this->updateProgress($jobId, $progress);
    }

    public function markJobFailed(string $jobId, string $message = 'Job failed', ?\Throwable $exception = null): void
    {
        $metadata = [];
        if ($exception) {
            $metadata['exception_type'] = get_class($exception);
            $metadata['exception_message'] = $exception->getMessage();
        }

        $progress = JobProgress::failed($message)->withMetadata($metadata);
        $this->updateProgress($jobId, $progress);
    }

    public function getProgressForJobs(array $jobIds): array
    {
        if (empty($jobIds)) {
            return [];
        }

        $placeholders = str_repeat('?,', count($jobIds) - 1) . '?';
        $sql = "
            SELECT DISTINCT jp1.*
            FROM job_progress jp1
            INNER JOIN (
                SELECT job_id, MAX(updated_at) as max_updated
                FROM job_progress
                WHERE job_id IN ({$placeholders})
                GROUP BY job_id
            ) jp2 ON jp1.job_id = jp2.job_id AND jp1.updated_at = jp2.max_updated
        ";

        $result = $this->connection->query(SqlQuery::create($sql, $jobIds));
        $rows = $result->fetchAll();

        $result = [];
        foreach ($rows as $row) {
            $entry = $this->mapRowToProgressEntry($row);
            $result[$entry->jobId] = $entry->getJobProgress();
        }

        return $result;
    }

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

        $sql = "DELETE FROM job_progress WHERE updated_at < ?";

        return $this->connection->execute(SqlQuery::create($sql, [$cutoffDate]));
    }

    public function getJobsAboveProgress(float $minPercentage): array
    {
        $sql = "
            SELECT DISTINCT jp1.*
            FROM job_progress jp1
            INNER JOIN (
                SELECT job_id, MAX(updated_at) as max_updated
                FROM job_progress
                WHERE percentage >= ?
                GROUP BY job_id
            ) jp2 ON jp1.job_id = jp2.job_id AND jp1.updated_at = jp2.max_updated
            WHERE jp1.percentage >= ?
            ORDER BY jp1.percentage DESC, jp1.updated_at DESC
        ";

        $result = $this->connection->query(SqlQuery::create($sql, [$minPercentage, $minPercentage]));
        $rows = $result->fetchAll();

        return array_map([$this, 'mapRowToProgressEntry'], $rows);
    }

    public function getRecentlyUpdatedJobs(int $limitMinutes = 60, int $limit = 100): array
    {
        $cutoffTime = date('Y-m-d H:i:s', strtotime("-{$limitMinutes} minutes"));

        $sql = "
            SELECT DISTINCT jp1.*
            FROM job_progress jp1
            INNER JOIN (
                SELECT job_id, MAX(updated_at) as max_updated
                FROM job_progress
                WHERE updated_at >= ?
                GROUP BY job_id
            ) jp2 ON jp1.job_id = jp2.job_id AND jp1.updated_at = jp2.max_updated
            ORDER BY jp1.updated_at DESC
            LIMIT ?
        ";

        $result = $this->connection->query(SqlQuery::create($sql, [$cutoffTime, $limit]));
        $rows = $result->fetchAll();

        return array_map([$this, 'mapRowToProgressEntry'], $rows);
    }

    private function mapRowToProgressEntry(array $row): JobProgressEntry
    {
        return new JobProgressEntry(
            id: $row['id'],
            jobId: $row['job_id'],
            percentage: (float) $row['percentage'],
            message: $row['message'],
            stepName: $row['step_name'],
            metadata: $row['metadata'],
            updatedAt: $row['updated_at'],
            isCompleted: (bool) $row['is_completed'],
            isFailed: (bool) $row['is_failed']
        );
    }
}
