<?php

namespace App\Tests\Service;

use App\Service\AnalyticsCryptoService;
use PHPUnit\Framework\TestCase;

class AnalyticsCryptoServiceTest extends TestCase
{
    private AnalyticsCryptoService $service;

    protected function setUp(): void
    {
        $this->service = new AnalyticsCryptoService('test-secret-key');
    }

    public function testEncryptReturnsBase64String(): void
    {
        $data = ['page' => '/home', 'visits' => 42];

        $encrypted = $this->service->encrypt($data);

        $this->assertNotEmpty($encrypted);
        $this->assertNotFalse(base64_decode($encrypted, true));
    }

    public function testEncryptThenDecryptReturnsOriginalData(): void
    {
        $data = ['page' => '/stats', 'count' => 10, 'nested' => ['a' => 1]];

        $encrypted = $this->service->encrypt($data);
        $decrypted = $this->service->decrypt($encrypted);

        $this->assertSame($data, $decrypted);
    }

    public function testDecryptInvalidBase64ReturnsNull(): void
    {
        $this->assertNull($this->service->decrypt('!!!not-base64!!!'));
    }

    public function testDecryptTooShortPayloadReturnsNull(): void
    {
        // Less than 28 bytes after decode
        $this->assertNull($this->service->decrypt(base64_encode('short')));
    }

    public function testDecryptCorruptedPayloadReturnsNull(): void
    {
        $data = ['test' => true];
        $encrypted = $this->service->encrypt($data);

        // Corrupt the payload by flipping bytes
        $raw = base64_decode($encrypted, true);
        $corrupted = $raw;
        $corrupted[20] = chr(ord($corrupted[20]) ^ 0xFF);
        $corrupted[21] = chr(ord($corrupted[21]) ^ 0xFF);

        $this->assertNull($this->service->decrypt(base64_encode($corrupted)));
    }

    public function testDecryptJsFormatWithShortCiphertextReturnsNull(): void
    {
        // 28 bytes of zeros — neither JS nor PHP format can decrypt
        $raw = str_repeat("\x00", 28);

        $this->assertNull($this->service->decrypt(base64_encode($raw)));
    }

    public function testTryDecryptJsFormatWithTooShortInput(): void
    {
        $method = new \ReflectionMethod(AnalyticsCryptoService::class, 'tryDecryptJsFormat');

        $result = $method->invoke($this->service, str_repeat("\x00", 12), 'short');

        $this->assertNull($result);
    }

    public function testDecryptPhpFormatFallback(): void
    {
        // Build a PHP-format payload: iv (12) + tag (16) + ciphertext
        $data = ['fallback' => true];
        $json = json_encode($data);
        $key = substr(hash('sha256', 'test-secret-key', true), 0, 32);
        $iv = random_bytes(12);
        $encrypted = openssl_encrypt($json, 'aes-256-gcm', $key, \OPENSSL_RAW_DATA, $iv, $tag, '', 16);

        // PHP format: iv + tag + ciphertext
        $phpFormat = base64_encode($iv . $tag . $encrypted);

        $this->assertSame($data, $this->service->decrypt($phpFormat));
    }

    public function testGenerateVisitorHashReturnsDeterministicHash(): void
    {
        $hash1 = $this->service->generateVisitorHash('user-123');
        $hash2 = $this->service->generateVisitorHash('user-123');

        $this->assertSame($hash1, $hash2);
        $this->assertSame(64, \strlen($hash1)); // SHA-256 hex = 64 chars
    }

    public function testGenerateVisitorHashDiffersForDifferentUids(): void
    {
        $hash1 = $this->service->generateVisitorHash('user-123');
        $hash2 = $this->service->generateVisitorHash('user-456');

        $this->assertNotSame($hash1, $hash2);
    }

    public function testVerifyVisitorHashReturnsTrueForValidHash(): void
    {
        $uid = 'visitor-abc';
        $hash = $this->service->generateVisitorHash($uid);

        $this->assertTrue($this->service->verifyVisitorHash($uid, $hash));
    }

    public function testVerifyVisitorHashReturnsFalseForInvalidHash(): void
    {
        $this->assertFalse($this->service->verifyVisitorHash('visitor-abc', 'wrong-hash'));
    }

    public function testGetKeyForJsReturnsBase64EncodedKey(): void
    {
        $jsKey = $this->service->getKeyForJs();

        $this->assertNotEmpty($jsKey);
        $decoded = base64_decode($jsKey, true);
        $this->assertNotFalse($decoded);
        $this->assertSame(32, \strlen($decoded)); // AES-256 key = 32 bytes
    }

    public function testDifferentSecretsProduceDifferentKeys(): void
    {
        $service2 = new AnalyticsCryptoService('different-secret');

        $this->assertNotSame($this->service->getKeyForJs(), $service2->getKeyForJs());
    }

    public function testEncryptedByOneServiceCannotBeDecryptedByAnother(): void
    {
        $service2 = new AnalyticsCryptoService('different-secret');
        $data = ['secret' => 'data'];

        $encrypted = $this->service->encrypt($data);

        $this->assertNull($service2->decrypt($encrypted));
    }
}
