<?php

namespace MintyPHP\Tests\Architecture;

use PHPUnit\Framework\TestCase;

class FrontendTelemetryContractTest extends TestCase
{
    use ProjectFileAssertionSupport;

    public function testEventTypeMappingsStayInSyncAcrossFrontendAndBackend(): void
    {
        $js = $this->readProjectFile('web/js/core/app-telemetry.js');
        $ingest = $this->readProjectFile('lib/Service/Audit/FrontendTelemetryIngestService.php');
        $settings = $this->readProjectFile('lib/Service/Settings/SettingsFrontendTelemetryGateway.php');

        $jsMap = $this->parseJsEventKeyToTypeMap($js);
        $phpMap = $this->parsePhpEventTypeToKeyMap($ingest);

        $expectedPhpMap = array_flip($jsMap);
        ksort($expectedPhpMap);
        ksort($phpMap);
        $this->assertSame($expectedPhpMap, $phpMap);

        $settingsAllowedEvents = $this->parsePhpStringArrayConstant(
            $settings,
            'FRONTEND_TELEMETRY_ALLOWED_EVENTS'
        );
        sort($settingsAllowedEvents, SORT_STRING);
        $expectedAllowedEvents = array_keys($jsMap);
        sort($expectedAllowedEvents, SORT_STRING);
        $this->assertSame($expectedAllowedEvents, $settingsAllowedEvents);
    }

    public function testAllowedMetaKeysStayInSyncAcrossFrontendAndBackend(): void
    {
        $js = $this->readProjectFile('web/js/core/app-telemetry.js');
        $ingest = $this->readProjectFile('lib/Service/Audit/FrontendTelemetryIngestService.php');

        $jsKeys = $this->parseJsStringSet($js, 'ALLOWED_META_KEYS');
        $phpKeys = $this->parsePhpStringArrayConstant($ingest, 'ALLOWED_META_KEYS');

        sort($jsKeys, SORT_STRING);
        sort($phpKeys, SORT_STRING);
        $this->assertSame($jsKeys, $phpKeys);
    }

    public function testLengthLimitsStayInSyncAcrossFrontendAndBackend(): void
    {
        $js = $this->readProjectFile('web/js/core/app-telemetry.js');
        $ingest = $this->readProjectFile('lib/Service/Audit/FrontendTelemetryIngestService.php');

        $this->assertSame(
            $this->parseJsIntConstant($js, 'MAX_MESSAGE_LENGTH'),
            $this->parsePhpIntConstant($ingest, 'MESSAGE_MAX_LENGTH')
        );
        $this->assertSame(
            $this->parseJsIntConstant($js, 'MAX_FINGERPRINT_LENGTH'),
            $this->parsePhpIntConstant($ingest, 'FINGERPRINT_MAX_LENGTH')
        );
        $this->assertSame(
            $this->parseJsIntConstant($js, 'MAX_META_ENTRIES'),
            $this->parsePhpIntConstant($ingest, 'META_MAX_ENTRIES')
        );
        $this->assertSame(
            $this->parseJsIntConstant($js, 'MAX_META_VALUE_LENGTH'),
            $this->parsePhpIntConstant($ingest, 'META_VALUE_MAX_LENGTH')
        );
    }

    /**
     * @return array<string,string>
     */
    private function parseJsEventKeyToTypeMap(string $content): array
    {
        if (preg_match('/const\s+EVENT_KEY_TO_TYPE\s*=\s*Object\.freeze\(\{(.*?)\}\);/s', $content, $match) !== 1) {
            $this->fail('Could not parse EVENT_KEY_TO_TYPE from JS telemetry module.');
        }

        $map = [];
        preg_match_all('/([a-z_]+)\s*:\s*\'([^\']+)\'/', $match[1], $pairs, PREG_SET_ORDER);
        foreach ($pairs as $pair) {
            $map[$pair[1]] = $pair[2];
        }

        $this->assertNotSame([], $map, 'EVENT_KEY_TO_TYPE map is empty.');
        return $map;
    }

    /**
     * @return array<string,string>
     */
    private function parsePhpEventTypeToKeyMap(string $content): array
    {
        $constValues = [];
        preg_match_all('/private const\s+([A-Z0-9_]+)\s*=\s*\'([^\']+)\';/', $content, $constants, PREG_SET_ORDER);
        foreach ($constants as $constant) {
            $constValues[$constant[1]] = $constant[2];
        }

        if (preg_match('/private const EVENT_KEY_MAP = \[(.*?)\];/s', $content, $match) !== 1) {
            $this->fail('Could not parse EVENT_KEY_MAP from ingest service.');
        }

        $map = [];
        preg_match_all('/self::([A-Z0-9_]+)\s*=>\s*\'([a-z_]+)\'/', $match[1], $pairs, PREG_SET_ORDER);
        foreach ($pairs as $pair) {
            $eventConstant = $pair[1];
            $eventType = $constValues[$eventConstant] ?? '';
            $this->assertNotSame('', $eventType, 'Missing event constant for EVENT_KEY_MAP entry: ' . $eventConstant);
            $map[$eventType] = $pair[2];
        }

        $this->assertNotSame([], $map, 'EVENT_KEY_MAP is empty.');
        return $map;
    }

    /**
     * @return list<string>
     */
    private function parseJsStringSet(string $content, string $constantName): array
    {
        $pattern = '/const\s+' . preg_quote($constantName, '/') . '\s*=\s*new Set\(\[(.*?)\]\);/s';
        if (preg_match($pattern, $content, $match) !== 1) {
            $this->fail('Could not parse JS Set constant: ' . $constantName);
        }

        $values = [];
        preg_match_all('/\'([^\']+)\'/', $match[1], $items, PREG_SET_ORDER);
        foreach ($items as $item) {
            $values[] = $item[1];
        }

        return array_values(array_unique($values));
    }

    /**
     * @return list<string>
     */
    private function parsePhpStringArrayConstant(string $content, string $constantName): array
    {
        $pattern = '/private const\s+' . preg_quote($constantName, '/') . '\s*=\s*\[(.*?)\];/s';
        if (preg_match($pattern, $content, $match) !== 1) {
            $this->fail('Could not parse PHP array constant: ' . $constantName);
        }

        $values = [];
        preg_match_all('/\'([^\']+)\'/', $match[1], $items, PREG_SET_ORDER);
        foreach ($items as $item) {
            $values[] = $item[1];
        }

        return array_values(array_unique($values));
    }

    private function parseJsIntConstant(string $content, string $constantName): int
    {
        $pattern = '/const\s+' . preg_quote($constantName, '/') . '\s*=\s*([0-9]+)\s*;/';
        if (preg_match($pattern, $content, $match) !== 1) {
            $this->fail('Could not parse JS integer constant: ' . $constantName);
        }
        return (int) $match[1];
    }

    private function parsePhpIntConstant(string $content, string $constantName): int
    {
        $pattern = '/private const\s+' . preg_quote($constantName, '/') . '\s*=\s*([0-9]+)\s*;/';
        if (preg_match($pattern, $content, $match) !== 1) {
            $this->fail('Could not parse PHP integer constant: ' . $constantName);
        }
        return (int) $match[1];
    }
}
