<?php

namespace App\Command;

use App\Entity\Service;
use App\Repository\ServiceRepository;
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\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;
use Symfony\Contracts\HttpClient\HttpClientInterface;

#[AsCommand(
    name: 'app:services:check',
    description: 'Verifie le statut de tous les services externes et met a jour leur status',
)]
class CheckServicesCommand extends Command
{
    public function __construct(
        private ServiceRepository $serviceRepository,
        private EntityManagerInterface $em,
        private HttpClientInterface $httpClient,
    ) {
        parent::__construct();
    }

    protected function execute(InputInterface $input, OutputInterface $output): int
    {
        $io = new SymfonyStyle($input, $output);

        $services = $this->serviceRepository->createQueryBuilder('s')
            ->where('s.url IS NOT NULL')
            ->andWhere('s.url != :empty')
            ->setParameter('empty', '')
            ->getQuery()
            ->getResult();

        if (0 === \count($services)) {
            $io->info('Aucun service a verifier.');

            return Command::SUCCESS;
        }

        $checked = 0;
        $changed = 0;

        foreach ($services as $service) {
            $oldStatus = $service->getStatus();

            // Skip les services "soon" ou en maintenance manuelle
            if (\in_array($oldStatus, [Service::STATUS_SOON, Service::STATUS_MAINTENANCE], true)) {
                $io->comment('['.$service->getName().'] Skip ('.$oldStatus.')');
                continue;
            }

            $newStatus = $this->checkService($service);
            ++$checked;

            if ($newStatus !== $oldStatus) {
                $message = 'Auto-detection: '.$oldStatus.' -> '.$newStatus;
                $service->setStatus($newStatus, $message);
                ++$changed;
                $io->warning('['.$service->getName().'] '.$message);
            } else {
                $io->info('['.$service->getName().'] OK ('.$newStatus.')');
            }
        }

        // Check Stripe via status page
        $this->checkStripe($services, $io, $changed);

        $this->em->flush();

        $io->success(sprintf('%d service(s) verifie(s), %d changement(s).', $checked, $changed));

        return Command::SUCCESS;
    }

    private function checkService(Service $service): string
    {
        $type = $service->getExternalType();

        return match ($type) {
            'docuseal' => $this->checkDocuSeal($service),
            'vault' => $this->checkVault($service),
            'minio' => $this->checkMinio($service),
            default => $this->checkHttp($service),
        };
    }

    private function checkHttp(Service $service): string
    {
        try {
            $response = $this->httpClient->request('GET', $service->getUrl(), [
                'timeout' => 10,
                'max_redirects' => 3,
            ]);

            $statusCode = $response->getStatusCode();

            $status = match (true) {
                $statusCode >= 200 && $statusCode < 400 => Service::STATUS_UP,
                $statusCode >= 500 => Service::STATUS_DOWN,
                default => Service::STATUS_DEGRADED,
            };
        } catch (\Throwable) {
            $status = Service::STATUS_DOWN;
        }

        return $status;
    }

    private function checkDocuSeal(Service $service): string
    {
        try {
            $response = $this->httpClient->request('GET', rtrim($service->getUrl(), '/').'/up', [
                'timeout' => 10,
            ]);

            $status = 200 === $response->getStatusCode() ? Service::STATUS_UP : Service::STATUS_DOWN;
        } catch (\Throwable) {
            $status = Service::STATUS_DOWN;
        }

        return $status;
    }

    private function checkVault(Service $service): string
    {
        try {
            $response = $this->httpClient->request('GET', rtrim($service->getUrl(), '/').'/v1/sys/health', [
                'timeout' => 10,
            ]);

            $statusCode = $response->getStatusCode();

            // Vault: 200 = initialized+unsealed, 429 = unsealed+standby, 472 = recovery, 501 = not initialized, 503 = sealed
            $status = match ($statusCode) {
                200 => Service::STATUS_UP,
                429 => Service::STATUS_DEGRADED,
                default => Service::STATUS_DOWN,
            };
        } catch (\Throwable) {
            $status = Service::STATUS_DOWN;
        }

        return $status;
    }

    private function checkMinio(Service $service): string
    {
        try {
            $response = $this->httpClient->request('GET', rtrim($service->getUrl(), '/').'/minio/health/live', [
                'timeout' => 10,
            ]);

            $status = 200 === $response->getStatusCode() ? Service::STATUS_UP : Service::STATUS_DOWN;
        } catch (\Throwable) {
            $status = Service::STATUS_DOWN;
        }

        return $status;
    }

    /**
     * Check Stripe status via leur status page API.
     *
     * @param list<Service> $services
     */
    private function checkStripe(array $services, SymfonyStyle $io, int &$changed): void
    {
        $stripeServices = array_filter($services, fn (Service $s) => 'stripe' === $s->getExternalType());

        if ([] === $stripeServices) {
            return;
        }

        try {
            $response = $this->httpClient->request('GET', 'https://status.stripe.com/api/v2/status.json', [
                'timeout' => 10,
            ]);

            $data = $response->toArray(false);
            $indicator = $data['status']['indicator'] ?? 'none';

            $status = match ($indicator) {
                'none' => Service::STATUS_UP,
                'minor' => Service::STATUS_DEGRADED,
                'major', 'critical' => Service::STATUS_DOWN,
                default => Service::STATUS_UP,
            };

            $description = $data['status']['description'] ?? '';

            foreach ($stripeServices as $service) {
                $oldStatus = $service->getStatus();
                if ($status !== $oldStatus) {
                    $service->setStatus($status, 'Stripe: '.$description);
                    ++$changed;
                    $io->warning('[Stripe] '.$oldStatus.' -> '.$status.' ('.$description.')');
                }
            }
        } catch (\Throwable $e) {
            $io->error('[Stripe] Impossible de verifier : '.$e->getMessage());
        }
    }
}
