<?php

declare(strict_types=1);

namespace App\Command;

use App\Entity\Document;
use App\Repository\DocumentRepository;
use App\Service\DocumentStorageService;
use App\Service\PdfCompressorService;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;

use function count;
use function strlen;

#[AsCommand(
    name: 'app:compress-pdf',
    description: 'Compress all PDF documents without quality loss',
)]
class CompressPdfCommand extends Command
{
    public function __construct(
        private readonly DocumentRepository $documentRepository,
        private readonly EntityManagerInterface $em,
        private readonly PdfCompressorService $pdfCompressor,
        private readonly DocumentStorageService $storageService,
    ) {
        parent::__construct();
    }

    protected function configure(): void
    {
        $this
            ->addOption('dry-run', null, InputOption::VALUE_NONE, 'Show what would be compressed without actually doing it')
        ;
    }

    protected function execute(InputInterface $input, OutputInterface $output): int
    {
        $io     = new SymfonyStyle($input, $output);
        $dryRun = $input->getOption('dry-run');

        // Check if qpdf is installed
        exec('which qpdf', $qpdfPath, $returnCode);
        if (0 !== $returnCode) {
            $io->error('qpdf is not installed. Run: sudo apt install qpdf');

            return Command::FAILURE;
        }

        $documents = $this->documentRepository->findBy(['mimeType' => 'application/pdf']);

        if (empty($documents)) {
            $io->info('No PDF documents found.');

            return Command::SUCCESS;
        }

        $io->title('PDF Compression');
        $io->text(sprintf('Found %d PDF documents', count($documents)));

        $totalSaved = 0;
        $compressed = 0;

        foreach ($documents as $document) {
            $path = $document->getPath();

            if ($this->storageService->isBase64DataUri($path)) {
                $this->compressBase64Document($document, $path, $dryRun, $io, $totalSaved, $compressed);
            } else {
                $this->compressFileDocument($document, $path, $dryRun, $io, $totalSaved, $compressed);
            }
        }

        if (!$dryRun && $compressed > 0) {
            $this->em->flush();
            $io->success(sprintf(
                'Compressed %d/%d PDFs. Total space saved: %s',
                $compressed,
                count($documents),
                $this->formatBytes($totalSaved)
            ));
        } elseif ($dryRun) {
            $io->info('Dry run completed. No changes made.');
        } else {
            $io->info('No PDFs needed compression.');
        }

        return Command::SUCCESS;
    }

    private function compressBase64Document(
        Document $document,
        string $path,
        bool $dryRun,
        SymfonyStyle $io,
        int &$totalSaved,
        int &$compressed,
    ): void {
        $base64Data = $path;
        if (str_contains($base64Data, ',')) {
            $base64Data = explode(',', $base64Data, 2)[1];
        }

        $pdfContent = base64_decode($base64Data, true);
        if (false === $pdfContent) {
            $io->warning(sprintf('Failed to decode document: %s', $document->getName()));

            return;
        }

        $originalSize = strlen($pdfContent);

        if ($dryRun) {
            $io->text(sprintf(
                '  [DRY-RUN] Would compress (base64): %s (%s)',
                $document->getName(),
                $this->formatBytes($originalSize)
            ));

            return;
        }

        $result = $this->pdfCompressor->compressBase64Pdf($path);
        if (null !== $result) {
            $document->setPath($result['path']);
            $document->setSize($result['size']);
            $totalSaved += $result['saved'];
            ++$compressed;

            $io->text(sprintf(
                '  OK %s: %s → %s (-%s, -%.1f%%)',
                $document->getName(),
                $this->formatBytes($result['originalSize']),
                $this->formatBytes($result['size']),
                $this->formatBytes($result['saved']),
                ($result['saved'] / $result['originalSize']) * 100
            ));
        } else {
            $io->text(sprintf(
                '  - %s: Already optimal (%s)',
                $document->getName(),
                $this->formatBytes($originalSize)
            ));
        }
    }

    private function compressFileDocument(
        Document $document,
        string $path,
        bool $dryRun,
        SymfonyStyle $io,
        int &$totalSaved,
        int &$compressed,
    ): void {
        $absolutePath = $this->storageService->getAbsolutePath($path);
        if (!file_exists($absolutePath)) {
            $io->warning(sprintf('File not found: %s (%s)', $document->getName(), $path));

            return;
        }

        $originalSize = filesize($absolutePath);
        if (false === $originalSize) {
            return;
        }

        if ($dryRun) {
            $io->text(sprintf(
                '  [DRY-RUN] Would compress (file): %s (%s)',
                $document->getName(),
                $this->formatBytes($originalSize)
            ));

            return;
        }

        $result = $this->pdfCompressor->compressFile($absolutePath);
        if (null !== $result) {
            $document->setSize($result['size']);
            $totalSaved += $result['saved'];
            ++$compressed;

            $io->text(sprintf(
                '  OK %s: %s → %s (-%s, -%.1f%%)',
                $document->getName(),
                $this->formatBytes($result['originalSize']),
                $this->formatBytes($result['size']),
                $this->formatBytes($result['saved']),
                ($result['saved'] / $result['originalSize']) * 100
            ));
        } else {
            $io->text(sprintf(
                '  - %s: Already optimal (%s)',
                $document->getName(),
                $this->formatBytes($originalSize)
            ));
        }
    }

    private function formatBytes(int $bytes): string
    {
        $units = ['B', 'KB', 'MB', 'GB'];
        $i     = 0;
        while ($bytes >= 1024 && $i < count($units) - 1) {
            $bytes /= 1024;
            ++$i;
        }

        return round($bytes, 2).' '.$units[$i];
    }
}
