<?php

namespace App\Tests\Command;

use App\Command\TranslateCommand;
use PHPUnit\Framework\TestCase;
use Symfony\Component\Console\Application;
use Symfony\Component\Console\Tester\CommandTester;
use Symfony\Component\Yaml\Yaml;
use Symfony\Contracts\HttpClient\HttpClientInterface;
use Symfony\Contracts\HttpClient\ResponseInterface;

class TranslateCommandTest extends TestCase
{
    private string $tmpDir;
    private string $translationsDir;

    protected function setUp(): void
    {
        $this->tmpDir = sys_get_temp_dir().'/translate_test_'.uniqid();
        $this->translationsDir = $this->tmpDir.'/translations';
        mkdir($this->translationsDir, 0o755, true);
    }

    protected function tearDown(): void
    {
        $files = glob($this->translationsDir.'/*') ?: [];
        foreach ($files as $f) {
            unlink($f);
        }
        rmdir($this->translationsDir);
        rmdir($this->tmpDir);
    }

    private function createTester(HttpClientInterface $httpClient): CommandTester
    {
        $command = new TranslateCommand($httpClient, $this->tmpDir);
        $app = new Application();
        $app->addCommand($command);

        return new CommandTester($app->find('app:translate'));
    }

    private function mockHttpClient(array $translateResponses = [], bool $apiAvailable = true): HttpClientInterface
    {
        $httpClient = $this->createMock(HttpClientInterface::class);
        $callIndex = 0;

        $httpClient->method('request')->willReturnCallback(
            function (string $method, string $url) use (&$callIndex, $translateResponses, $apiAvailable) {
                $response = $this->createMock(ResponseInterface::class);

                if ('GET' === $method && str_contains($url, '/languages')) {
                    if (!$apiAvailable) {
                        throw new \RuntimeException('Connection refused');
                    }
                    $response->method('getStatusCode')->willReturn(200);

                    return $response;
                }

                if ('POST' === $method && str_contains($url, '/translate')) {
                    $translated = $translateResponses[$callIndex] ?? 'Translated text';
                    ++$callIndex;
                    $response->method('toArray')->willReturn(['translatedText' => $translated]);

                    return $response;
                }

                return $response;
            }
        );

        return $httpClient;
    }

    public function testApiUnreachable(): void
    {
        $httpClient = $this->mockHttpClient([], false);
        $tester = $this->createTester($httpClient);

        $tester->execute(['--url' => 'http://fake:5000']);

        self::assertSame(1, $tester->getStatusCode());
        self::assertStringContainsString('not reachable', $tester->getDisplay());
    }

    public function testNoFrenchFiles(): void
    {
        $httpClient = $this->mockHttpClient();
        $tester = $this->createTester($httpClient);

        $tester->execute(['--url' => 'http://fake:5000']);

        self::assertSame(0, $tester->getStatusCode());
        self::assertStringContainsString('No French translation files', $tester->getDisplay());
    }

    public function testEmptyFrenchFile(): void
    {
        file_put_contents($this->translationsDir.'/messages.fr.yaml', '');

        $httpClient = $this->mockHttpClient();
        $tester = $this->createTester($httpClient);

        $tester->execute(['--url' => 'http://fake:5000']);

        self::assertSame(0, $tester->getStatusCode());
        self::assertStringContainsString('Skipped', $tester->getDisplay());
    }

    public function testTranslatesNewKeys(): void
    {
        file_put_contents($this->translationsDir.'/messages.fr.yaml', Yaml::dump([
            'hello' => 'Bonjour',
            'goodbye' => 'Au revoir',
        ]));

        // 2 keys × 4 languages = 8 translations
        $responses = array_fill(0, 8, null);
        $responses[0] = 'Hello';
        $responses[1] = 'Goodbye';
        $responses[2] = 'Hola';
        $responses[3] = 'Adios';
        $responses[4] = 'Hallo';
        $responses[5] = 'Auf Wiedersehen';
        $responses[6] = 'Ciao';
        $responses[7] = 'Arrivederci';

        $httpClient = $this->mockHttpClient($responses);
        $tester = $this->createTester($httpClient);

        $tester->execute(['--url' => 'http://fake:5000']);

        self::assertSame(0, $tester->getStatusCode());
        self::assertStringContainsString('Translated 2 new keys', $tester->getDisplay());

        // Check EN file
        $enData = Yaml::parseFile($this->translationsDir.'/messages.en.yaml');
        self::assertSame('Hello', $enData['hello']);
        self::assertSame('Goodbye', $enData['goodbye']);

        // Check ES file
        $esData = Yaml::parseFile($this->translationsDir.'/messages.es.yaml');
        self::assertSame('Hola', $esData['hello']);

        // Check DE file
        self::assertFileExists($this->translationsDir.'/messages.de.yaml');

        // Check IT file
        self::assertFileExists($this->translationsDir.'/messages.it.yaml');
    }

    public function testSkipsExistingKeys(): void
    {
        file_put_contents($this->translationsDir.'/messages.fr.yaml', Yaml::dump([
            'hello' => 'Bonjour',
            'new_key' => 'Nouveau',
        ]));

        file_put_contents($this->translationsDir.'/messages.en.yaml', Yaml::dump([
            'hello' => 'Hello (manual)',
        ]));

        // Only new_key needs translating for EN, then 2 keys for ES/DE/IT = 7
        $responses = [];
        $responses[0] = 'New';         // new_key → EN
        $responses[1] = 'Hola';        // hello → ES
        $responses[2] = 'Nuevo';       // new_key → ES
        $responses[3] = 'Hallo';       // hello → DE
        $responses[4] = 'Neu';         // new_key → DE
        $responses[5] = 'Ciao';        // hello → IT
        $responses[6] = 'Nuovo';       // new_key → IT

        $httpClient = $this->mockHttpClient($responses);
        $tester = $this->createTester($httpClient);

        $tester->execute(['--url' => 'http://fake:5000']);

        self::assertSame(0, $tester->getStatusCode());

        $enData = Yaml::parseFile($this->translationsDir.'/messages.en.yaml');
        self::assertSame('Hello (manual)', $enData['hello']);
        self::assertSame('New', $enData['new_key']);
    }

    public function testRemovesObsoleteKeys(): void
    {
        file_put_contents($this->translationsDir.'/messages.fr.yaml', Yaml::dump([
            'hello' => 'Bonjour',
        ]));

        file_put_contents($this->translationsDir.'/messages.en.yaml', Yaml::dump([
            'hello' => 'Hello',
            'removed_key' => 'This was removed from FR',
        ]));

        $httpClient = $this->mockHttpClient();
        $tester = $this->createTester($httpClient);

        $tester->execute(['--url' => 'http://fake:5000']);

        self::assertSame(0, $tester->getStatusCode());

        $enData = Yaml::parseFile($this->translationsDir.'/messages.en.yaml');
        self::assertArrayHasKey('hello', $enData);
        self::assertArrayNotHasKey('removed_key', $enData);
        self::assertStringContainsString('Removed 1 obsolete', $tester->getDisplay());
    }

    public function testAlreadyUpToDate(): void
    {
        file_put_contents($this->translationsDir.'/messages.fr.yaml', Yaml::dump([
            'hello' => 'Bonjour',
        ]));

        foreach (['en', 'es', 'de', 'it'] as $lang) {
            file_put_contents($this->translationsDir.'/messages.'.$lang.'.yaml', Yaml::dump([
                'hello' => 'Translated',
            ]));
        }

        $httpClient = $this->mockHttpClient();
        $tester = $this->createTester($httpClient);

        $tester->execute(['--url' => 'http://fake:5000']);

        self::assertSame(0, $tester->getStatusCode());
        self::assertStringContainsString('Already up to date', $tester->getDisplay());
    }

    public function testPreservesPlaceholders(): void
    {
        file_put_contents($this->translationsDir.'/messages.fr.yaml', Yaml::dump([
            'retry' => 'Reessayez dans %minutes% minutes.',
        ]));

        // LibreTranslate might mangle the placeholder
        $responses = [
            'Try again in % minutes % minutes.',  // EN - mangled
            'Inténtelo de nuevo en %minutes% minutos.',  // ES - kept
            'Versuchen Sie es in % minuten % Minuten erneut.',  // DE - mangled
            'Riprova tra %minutes% minuti.',  // IT - kept
        ];

        $httpClient = $this->mockHttpClient($responses);
        $tester = $this->createTester($httpClient);

        $tester->execute(['--url' => 'http://fake:5000']);

        self::assertSame(0, $tester->getStatusCode());

        $esData = Yaml::parseFile($this->translationsDir.'/messages.es.yaml');
        self::assertStringContainsString('%minutes%', $esData['retry']);
    }

    public function testTranslationApiFailureFallbacksToOriginal(): void
    {
        file_put_contents($this->translationsDir.'/messages.fr.yaml', Yaml::dump([
            'error' => 'Une erreur est survenue.',
        ]));

        $httpClient = $this->createMock(HttpClientInterface::class);
        $callCount = 0;

        $httpClient->method('request')->willReturnCallback(
            function (string $method, string $url) use (&$callCount) {
                $response = $this->createMock(ResponseInterface::class);

                if ('GET' === $method && str_contains($url, '/languages')) {
                    $response->method('getStatusCode')->willReturn(200);

                    return $response;
                }

                if ('POST' === $method && str_contains($url, '/translate')) {
                    ++$callCount;
                    throw new \RuntimeException('API error');
                }

                return $response;
            }
        );

        $tester = $this->createTester($httpClient);
        $tester->execute(['--url' => 'http://fake:5000']);

        self::assertSame(0, $tester->getStatusCode());
        self::assertStringContainsString('Failed to translate', $tester->getDisplay());

        // Fallback: original FR text used
        $enData = Yaml::parseFile($this->translationsDir.'/messages.en.yaml');
        self::assertSame('Une erreur est survenue.', $enData['error']);
    }

    public function testMultipleDomainFiles(): void
    {
        file_put_contents($this->translationsDir.'/messages.fr.yaml', Yaml::dump([
            'hello' => 'Bonjour',
        ]));
        file_put_contents($this->translationsDir.'/security.fr.yaml', Yaml::dump([
            'login' => 'Connexion',
        ]));

        $httpClient = $this->mockHttpClient(array_fill(0, 8, 'Translated'));
        $tester = $this->createTester($httpClient);

        $tester->execute(['--url' => 'http://fake:5000']);

        self::assertSame(0, $tester->getStatusCode());

        self::assertFileExists($this->translationsDir.'/messages.en.yaml');
        self::assertFileExists($this->translationsDir.'/security.en.yaml');
        self::assertFileExists($this->translationsDir.'/messages.it.yaml');
        self::assertFileExists($this->translationsDir.'/security.it.yaml');
    }

    public function testKeysOrderMatchesFrench(): void
    {
        file_put_contents($this->translationsDir.'/messages.fr.yaml', Yaml::dump([
            'aaa' => 'Premier',
            'zzz' => 'Dernier',
            'mmm' => 'Milieu',
        ]));

        $responses = ['First', 'Last', 'Middle'];
        // Repeat for 4 languages
        $allResponses = array_merge($responses, $responses, $responses, $responses);

        $httpClient = $this->mockHttpClient($allResponses);
        $tester = $this->createTester($httpClient);

        $tester->execute(['--url' => 'http://fake:5000']);

        $enData = Yaml::parseFile($this->translationsDir.'/messages.en.yaml');
        $keys = array_keys($enData);

        self::assertSame(['aaa', 'zzz', 'mmm'], $keys);
    }
}
