<?php

declare(strict_types=1);

namespace App\Controller\Admin;

use App\Entity\IngestJob;
use App\Service\IngestJobService;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
use Symfony\Component\Routing\Attribute\Route;

#[Route('/admin/jobs')]
final class IngestJobController extends AbstractController
{
    #[Route('', name: 'admin_jobs', methods: ['GET'])]
    public function index(EntityManagerInterface $em): Response
    {
        $jobs = $em->getRepository(IngestJob::class)
            ->findBy([], ['startedAt' => 'DESC', 'id' => 'DESC']);

        return $this->render('admin/job/index.html.twig', [
            'jobs' => $jobs,
        ]);
    }

    #[Route(
        '/{id}',
        name: 'admin_job_show',
        requirements: ['id' => '[0-9a-fA-F\-]{36}'],
        methods: ['GET']
    )]
    public function show(string $id, EntityManagerInterface $em): Response
    {
        return $this->render('admin/job/show.html.twig', [
            'job' => $this->findJob($id, $em),
        ]);
    }

    #[Route(
        '/{id}/status',
        name: 'admin_job_status',
        requirements: ['id' => '[0-9a-fA-F\-]{36}'],
        methods: ['GET']
    )]
    public function status(string $id, EntityManagerInterface $em): JsonResponse
    {
        $this->denyAccessUnlessGranted('ROLE_USER');

        $job = $this->findJob($id, $em);

        return $this->json([
            'id' => (string) $job->getId(),
            'type' => $job->getType(),
            'status' => $job->getStatus(),
            'startedAt' => $job->getStartedAt()->format(DATE_ATOM),
            'finishedAt' => $job->getFinishedAt()?->format(DATE_ATOM),
            'errorMessage' => $job->getErrorMessage(),
            'logPath' => $job->getLogPath(),
        ]);
    }

    #[Route('/global-reindex', name: 'admin_global_reindex', methods: ['POST'])]
    public function globalReindex(
        Request $request,
        IngestJobService $jobService,
        EntityManagerInterface $em,
    ): RedirectResponse {
        $this->denyAccessUnlessGranted('ROLE_SUPER_ADMIN');

        if (!$this->isCsrfTokenValid('global_reindex', (string) $request->request->get('_token'))) {
            $this->addFlash('danger', 'Ungültiges CSRF-Token.');

            return $this->redirectToRoute('admin_jobs');
        }

        try {
            $projectDir = $this->resolveProjectDir();
            $console = $projectDir . '/bin/console';

            if (!is_file($console)) {
                throw new \RuntimeException('bin/console not found: ' . $console);
            }

            $logDir = $projectDir . '/var/log/ingest';
            $this->ensureDirectoryExists($logDir);

            $job = $jobService->startJob(
                IngestJob::TYPE_GLOBAL_REINDEX,
                $this->getUser(),
                null,
                null,
                null,
                IngestJob::STATUS_QUEUED
            );

            $logFile = $logDir . '/job_' . (string) $job->getId() . '.log';
            $job->setLogPath($logFile);
            $em->flush();

            $phpBinary = $this->resolvePhpBinary();
            $cmd = sprintf(
                'cd %s && nohup %s %s %s %s --no-interaction >> %s 2>&1 & echo $!',
                escapeshellarg($projectDir),
                escapeshellarg($phpBinary),
                escapeshellarg($console),
                escapeshellarg('mto:agent:ingest:run'),
                escapeshellarg((string) $job->getId()),
                escapeshellarg($logFile),
            );

            $output = [];
            $exitCode = 0;
            @exec($cmd, $output, $exitCode);

            if ($exitCode !== 0) {
                $job->markFailed('Global reindex async bootstrap failed with exit code ' . $exitCode . '.');
                $em->flush();

                $this->addFlash('danger', 'Global Reindex konnte nicht im Hintergrund gestartet werden.');

                return $this->redirectToRoute('admin_job_show', [
                    'id' => (string) $job->getId(),
                ]);
            }

            $this->addFlash('success', 'Global Reindex wurde gestartet.');

            return $this->redirectToRoute('admin_job_show', [
                'id' => (string) $job->getId(),
            ]);
        } catch (\Throwable $e) {
            $this->addFlash('danger', $this->buildSafeErrorMessage($e, 'Global Reindex konnte nicht gestartet werden.'));

            return $this->redirectToRoute('admin_jobs');
        }
    }

    private function findJob(string $id, EntityManagerInterface $em): IngestJob
    {
        $id = trim($id);

        /** @var IngestJob|null $job */
        $job = $em->getRepository(IngestJob::class)->find($id);

        if (!$job instanceof IngestJob) {
            throw new NotFoundHttpException();
        }

        return $job;
    }

    private function resolveProjectDir(): string
    {
        $projectDir = trim((string) $this->getParameter('kernel.project_dir'));

        if ($projectDir === '' || !is_dir($projectDir)) {
            throw new \RuntimeException('Project directory is invalid.');
        }

        return rtrim($projectDir, '/');
    }

    private function resolvePhpBinary(): string
    {
        $envCandidates = [
            trim((string) ($_SERVER['PHP_CLI_BINARY'] ?? '')),
            trim((string) ($_ENV['PHP_CLI_BINARY'] ?? '')),
            trim((string) getenv('PHP_CLI_BINARY')),
        ];

        foreach ($envCandidates as $candidate) {
            if ($this->isValidCliPhpBinary($candidate)) {
                return $candidate;
            }
        }

        $phpBinary = defined('PHP_BINARY') ? trim((string) PHP_BINARY) : '';
        if ($this->isValidCliPhpBinary($phpBinary)) {
            return $phpBinary;
        }

        $fallbackCandidates = [
            '/usr/bin/php',
            '/usr/local/bin/php',
            '/bin/php',
            '/opt/homebrew/bin/php',
        ];

        foreach ($fallbackCandidates as $candidate) {
            if ($this->isValidCliPhpBinary($candidate)) {
                return $candidate;
            }
        }

        $whichPhp = trim((string) @shell_exec('command -v php 2>/dev/null'));
        if ($this->isValidCliPhpBinary($whichPhp)) {
            return $whichPhp;
        }

        throw new \RuntimeException(
            'Could not resolve a CLI PHP binary. Set PHP_CLI_BINARY explicitly, e.g. /usr/bin/php.'
        );
    }

    private function isValidCliPhpBinary(string $path): bool
    {
        $path = trim($path);

        if ($path === '' || !is_file($path) || !is_executable($path)) {
            return false;
        }

        $basename = strtolower(basename($path));

        if (str_contains($basename, 'fpm') || str_contains($basename, 'cgi')) {
            return false;
        }

        return true;
    }

    private function ensureDirectoryExists(string $dir): void
    {
        if (is_dir($dir)) {
            return;
        }

        if (!@mkdir($dir, 0775, true) && !is_dir($dir)) {
            throw new \RuntimeException('Could not create ingest log directory.');
        }
    }

    private function buildSafeErrorMessage(\Throwable $e, string $fallback): string
    {
        $message = trim($e->getMessage());

        return $message !== '' ? $message : $fallback;
    }
}