<?php

namespace App\Tests\Command;

use App\Command\CheckServicesCommand;
use App\Entity\Service;
use App\Entity\ServiceCategory;
use App\Repository\ServiceRepository;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\Query;
use Doctrine\ORM\QueryBuilder;
use PHPUnit\Framework\TestCase;
use Symfony\Component\Console\Tester\CommandTester;
use Symfony\Contracts\HttpClient\HttpClientInterface;
use Symfony\Contracts\HttpClient\ResponseInterface;

class CheckServicesCommandTest extends TestCase
{
    private ServiceRepository $serviceRepository;
    private EntityManagerInterface $em;
    private HttpClientInterface $httpClient;

    protected function setUp(): void
    {
        $this->serviceRepository = $this->createStub(ServiceRepository::class);
        $this->em = $this->createStub(EntityManagerInterface::class);
        $this->httpClient = $this->createStub(HttpClientInterface::class);
    }

    private function execute(?EntityManagerInterface $em = null): CommandTester
    {
        $command = new CheckServicesCommand(
            $this->serviceRepository,
            $em ?? $this->em,
            $this->httpClient,
        );

        $tester = new CommandTester($command);
        $tester->execute([]);

        return $tester;
    }

    private function createService(string $name, string $url, string $status = 'up', ?string $externalType = null): Service
    {
        $category = new ServiceCategory('Test', 'test');
        $service = new Service($name, strtolower(str_replace(' ', '-', $name)), $category);
        $service->setUrl($url);
        $service->setStatus($status);
        if (null !== $externalType) {
            $service->setExternalType($externalType);
            $service->setIsExternal(true);
        }

        return $service;
    }

    private function mockQueryReturning(array $services): void
    {
        $query = $this->createStub(Query::class);
        $query->method('getResult')->willReturn($services);

        $qb = $this->createStub(QueryBuilder::class);
        $qb->method('where')->willReturnSelf();
        $qb->method('andWhere')->willReturnSelf();
        $qb->method('setParameter')->willReturnSelf();
        $qb->method('getQuery')->willReturn($query);

        $this->serviceRepository->method('createQueryBuilder')->willReturn($qb);
    }

    private function mockResponse(int $statusCode, array $json = []): ResponseInterface
    {
        $response = $this->createStub(ResponseInterface::class);
        $response->method('getStatusCode')->willReturn($statusCode);
        $response->method('toArray')->willReturn($json);

        return $response;
    }

    public function testNoServicesToCheck(): void
    {
        $this->mockQueryReturning([]);

        $tester = $this->execute();

        $this->assertSame(0, $tester->getStatusCode());
        $this->assertStringContainsString('Aucun service', $tester->getDisplay());
    }

    public function testServiceUpViaHttp(): void
    {
        $service = $this->createService('Web App', 'https://example.com');
        $this->mockQueryReturning([$service]);
        $this->httpClient->method('request')->willReturn($this->mockResponse(200));

        $tester = $this->execute();

        $this->assertSame(0, $tester->getStatusCode());
        $this->assertStringContainsString('1 service(s) verifie(s), 0 changement(s)', $tester->getDisplay());
    }

    public function testServiceDownViaHttp(): void
    {
        $service = $this->createService('Web App', 'https://example.com');
        $this->mockQueryReturning([$service]);
        $this->httpClient->method('request')->willReturn($this->mockResponse(500));

        $tester = $this->execute();

        $this->assertSame(0, $tester->getStatusCode());
        $this->assertStringContainsString('1 service(s) verifie(s), 1 changement(s)', $tester->getDisplay());
        $this->assertSame(Service::STATUS_DOWN, $service->getStatus());
    }

    public function testServiceDegradedViaHttp(): void
    {
        $service = $this->createService('Web App', 'https://example.com');
        $this->mockQueryReturning([$service]);
        $this->httpClient->method('request')->willReturn($this->mockResponse(403));

        $this->execute();

        $this->assertSame(Service::STATUS_DEGRADED, $service->getStatus());
    }

    public function testHttpExceptionMarksDown(): void
    {
        $service = $this->createService('Web App', 'https://example.com');
        $this->mockQueryReturning([$service]);
        $this->httpClient->method('request')->willThrowException(new \RuntimeException('timeout'));

        $this->execute();

        $this->assertSame(Service::STATUS_DOWN, $service->getStatus());
    }

    public function testSkipsSoonStatus(): void
    {
        $service = $this->createService('Future', 'https://example.com', Service::STATUS_SOON);
        $this->mockQueryReturning([$service]);

        $tester = $this->execute();

        $this->assertSame(0, $tester->getStatusCode());
        $this->assertStringContainsString('Skip', $tester->getDisplay());
        $this->assertStringContainsString('0 service(s) verifie(s)', $tester->getDisplay());
    }

    public function testSkipsMaintenanceStatus(): void
    {
        $service = $this->createService('Maint', 'https://example.com', Service::STATUS_MAINTENANCE);
        $this->mockQueryReturning([$service]);

        $tester = $this->execute();

        $this->assertStringContainsString('Skip', $tester->getDisplay());
    }

    public function testDocuSealCheckUp(): void
    {
        $service = $this->createService('DocuSeal', 'https://docuseal.example.com', 'up', 'docuseal');
        $this->mockQueryReturning([$service]);
        $this->httpClient->method('request')->willReturn($this->mockResponse(200));

        $this->execute();

        $this->assertSame(Service::STATUS_UP, $service->getStatus());
    }

    public function testDocuSealCheckDown(): void
    {
        $service = $this->createService('DocuSeal', 'https://docuseal.example.com', 'up', 'docuseal');
        $this->mockQueryReturning([$service]);
        $this->httpClient->method('request')->willReturn($this->mockResponse(503));

        $this->execute();

        $this->assertSame(Service::STATUS_DOWN, $service->getStatus());
    }

    public function testDocuSealCheckException(): void
    {
        $service = $this->createService('DocuSeal', 'https://docuseal.example.com', 'up', 'docuseal');
        $this->mockQueryReturning([$service]);
        $this->httpClient->method('request')->willThrowException(new \RuntimeException('fail'));

        $this->execute();

        $this->assertSame(Service::STATUS_DOWN, $service->getStatus());
    }

    public function testVaultCheckUp(): void
    {
        $service = $this->createService('Vault', 'https://vault.example.com', 'up', 'vault');
        $this->mockQueryReturning([$service]);
        $this->httpClient->method('request')->willReturn($this->mockResponse(200));

        $this->execute();

        $this->assertSame(Service::STATUS_UP, $service->getStatus());
    }

    public function testVaultCheckDegraded(): void
    {
        $service = $this->createService('Vault', 'https://vault.example.com', 'up', 'vault');
        $this->mockQueryReturning([$service]);
        $this->httpClient->method('request')->willReturn($this->mockResponse(429));

        $this->execute();

        $this->assertSame(Service::STATUS_DEGRADED, $service->getStatus());
    }

    public function testVaultCheckDown(): void
    {
        $service = $this->createService('Vault', 'https://vault.example.com', 'up', 'vault');
        $this->mockQueryReturning([$service]);
        $this->httpClient->method('request')->willReturn($this->mockResponse(503));

        $this->execute();

        $this->assertSame(Service::STATUS_DOWN, $service->getStatus());
    }

    public function testVaultCheckException(): void
    {
        $service = $this->createService('Vault', 'https://vault.example.com', 'up', 'vault');
        $this->mockQueryReturning([$service]);
        $this->httpClient->method('request')->willThrowException(new \RuntimeException('fail'));

        $this->execute();

        $this->assertSame(Service::STATUS_DOWN, $service->getStatus());
    }

    public function testMinioCheckUp(): void
    {
        $service = $this->createService('Minio', 'https://minio.example.com', 'up', 'minio');
        $this->mockQueryReturning([$service]);
        $this->httpClient->method('request')->willReturn($this->mockResponse(200));

        $this->execute();

        $this->assertSame(Service::STATUS_UP, $service->getStatus());
    }

    public function testMinioCheckDown(): void
    {
        $service = $this->createService('Minio', 'https://minio.example.com', 'up', 'minio');
        $this->mockQueryReturning([$service]);
        $this->httpClient->method('request')->willReturn($this->mockResponse(503));

        $this->execute();

        $this->assertSame(Service::STATUS_DOWN, $service->getStatus());
    }

    public function testMinioCheckException(): void
    {
        $service = $this->createService('Minio', 'https://minio.example.com', 'up', 'minio');
        $this->mockQueryReturning([$service]);
        $this->httpClient->method('request')->willThrowException(new \RuntimeException('fail'));

        $this->execute();

        $this->assertSame(Service::STATUS_DOWN, $service->getStatus());
    }

    public function testStripeCheckUpdatesStatus(): void
    {
        $service = $this->createService('Stripe', 'https://stripe.com', 'up', 'stripe');
        $this->mockQueryReturning([$service]);
        $this->httpClient->method('request')->willReturnCallback(function (string $method, string $url) {
            if (str_contains($url, 'status.stripe.com')) {
                return $this->mockResponse(200, [
                    'status' => ['indicator' => 'major', 'description' => 'Major outage'],
                ]);
            }

            return $this->mockResponse(200);
        });

        $this->execute();

        $this->assertSame(Service::STATUS_DOWN, $service->getStatus());
    }

    public function testStripeCheckMinor(): void
    {
        $service = $this->createService('Stripe', 'https://stripe.com', 'up', 'stripe');
        $this->mockQueryReturning([$service]);
        $this->httpClient->method('request')->willReturnCallback(function (string $method, string $url) {
            if (str_contains($url, 'status.stripe.com')) {
                return $this->mockResponse(200, [
                    'status' => ['indicator' => 'minor', 'description' => 'Minor issue'],
                ]);
            }

            return $this->mockResponse(200);
        });

        $this->execute();

        $this->assertSame(Service::STATUS_DEGRADED, $service->getStatus());
    }

    public function testStripeCheckNone(): void
    {
        $service = $this->createService('Stripe', 'https://stripe.com', 'up', 'stripe');
        $this->mockQueryReturning([$service]);
        $this->httpClient->method('request')->willReturnCallback(function (string $method, string $url) {
            if (str_contains($url, 'status.stripe.com')) {
                return $this->mockResponse(200, [
                    'status' => ['indicator' => 'none', 'description' => 'All good'],
                ]);
            }

            return $this->mockResponse(200);
        });

        $this->execute();

        $this->assertSame(Service::STATUS_UP, $service->getStatus());
    }

    public function testStripeCheckCritical(): void
    {
        $service = $this->createService('Stripe', 'https://stripe.com', 'up', 'stripe');
        $this->mockQueryReturning([$service]);
        $this->httpClient->method('request')->willReturnCallback(function (string $method, string $url) {
            if (str_contains($url, 'status.stripe.com')) {
                return $this->mockResponse(200, [
                    'status' => ['indicator' => 'critical', 'description' => 'Critical outage'],
                ]);
            }

            return $this->mockResponse(200);
        });

        $this->execute();

        $this->assertSame(Service::STATUS_DOWN, $service->getStatus());
    }

    public function testStripeCheckUnknownIndicator(): void
    {
        $service = $this->createService('Stripe', 'https://stripe.com', 'up', 'stripe');
        $this->mockQueryReturning([$service]);
        $this->httpClient->method('request')->willReturnCallback(function (string $method, string $url) {
            if (str_contains($url, 'status.stripe.com')) {
                return $this->mockResponse(200, [
                    'status' => ['indicator' => 'unknown_value', 'description' => 'Unknown'],
                ]);
            }

            return $this->mockResponse(200);
        });

        $this->execute();

        $this->assertSame(Service::STATUS_UP, $service->getStatus());
    }

    public function testStripeCheckException(): void
    {
        $service = $this->createService('Stripe', 'https://stripe.com', 'up', 'stripe');
        $this->mockQueryReturning([$service]);
        $this->httpClient->method('request')->willThrowException(new \RuntimeException('network error'));

        $tester = $this->execute();

        $this->assertStringContainsString('Impossible de verifier', $tester->getDisplay());
    }

    public function testFlushCalledAfterChecks(): void
    {
        $service = $this->createService('Web App', 'https://example.com');
        $this->mockQueryReturning([$service]);
        $this->httpClient->method('request')->willReturn($this->mockResponse(200));

        $em = $this->createMock(EntityManagerInterface::class);
        $em->expects($this->once())->method('flush');

        $this->execute($em);
    }

    public function testServiceStatusChangeMessage(): void
    {
        $service = $this->createService('API', 'https://api.example.com');
        $this->mockQueryReturning([$service]);
        $this->httpClient->method('request')->willReturn($this->mockResponse(500));

        $tester = $this->execute();

        $this->assertStringContainsString('Auto-detection', $tester->getDisplay());
        $this->assertStringContainsString('up -> down', $tester->getDisplay());
    }

    public function testHttpRedirect3xx(): void
    {
        $service = $this->createService('Redirect', 'https://example.com');
        $this->mockQueryReturning([$service]);
        $this->httpClient->method('request')->willReturn($this->mockResponse(301));

        $this->execute();

        $this->assertSame(Service::STATUS_UP, $service->getStatus());
    }
}
