<?php

namespace Shm\ShmAdmin\Utils;

use DateTimeImmutable;
use DateTimeZone;
use RuntimeException;
use Shm\ShmAdmin\AdminPanel;
use Shm\ShmUtils\Config;
use Shm\ShmUtils\ShmInit;
use Throwable;

class AdminLocalization
{
    private const SOURCE_LANG = 'ru';
    private const AWS_SERVICE = 'translate';
    private const AWS_TARGET = 'AWSShineFrontendService_20170701.TranslateText';
    private const AWS_CONTENT_TYPE = 'application/x-amz-json-1.1';

    private static function filePath(): string
    {
        return ShmInit::$rootDir . '/config/admin_localizations.json';
    }

    public static function currentData(): array
    {
        $file = self::filePath();
        if (!file_exists($file)) {
            file_put_contents($file, json_encode([], JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE));
        }
        return json_decode(file_get_contents($file), true);
    }

    private static function saveData(array $data): void
    {
        file_put_contents(
            self::filePath(),
            json_encode($data, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE)
        );
    }

    private static function needsTranslation(array $entry, string $lang): bool
    {
        if ($lang === self::SOURCE_LANG) {
            return false;
        }

        if (!isset($entry[self::SOURCE_LANG]) || trim((string) $entry[self::SOURCE_LANG]) === '') {
            return false;
        }

        if (!isset($entry[$lang])) {
            return true;
        }

        return trim((string) $entry[$lang]) === '';
    }

    private static function hasCyrillic(string $text): bool
    {
        return (bool) preg_match('/\p{Cyrillic}/u', $text);
    }

    private static function supportsCapitalization(string $lang): bool
    {
        return !in_array($lang, ['zh', 'ja', 'hi', 'bn'], true);
    }

    private static function startsWithUppercaseLetter(string $text): bool
    {
        if (!preg_match('/\p{L}/u', $text, $match)) {
            return false;
        }

        $letter = $match[0];

        return mb_strtoupper($letter, 'UTF-8') === $letter
            && mb_strtolower($letter, 'UTF-8') !== $letter;
    }

    private static function applySourceCapitalization(
        string $sourceText,
        string $translatedText,
        string $targetLang
    ): string {
        if (!self::supportsCapitalization($targetLang)) {
            return $translatedText;
        }

        if (!self::startsWithUppercaseLetter($sourceText)) {
            return $translatedText;
        }

        return preg_replace_callback(
            '/(\p{L})/u',
            function (array $match): string {
                $letter = $match[1];

                if (
                    mb_strtoupper($letter, 'UTF-8') === $letter
                    && mb_strtolower($letter, 'UTF-8') !== $letter
                ) {
                    return $letter;
                }

                return mb_strtoupper($letter, 'UTF-8');
            },
            $translatedText,
            1
        ) ?? $translatedText;
    }

    private static function awsCredentials(): ?array
    {
        $key = Config::get('aws.translate.key') ?? Config::get('s3.credentials.key');
        $secret = Config::get('aws.translate.secret') ?? Config::get('s3.credentials.secret');

        if (!$key || !$secret) {
            return null;
        }

        return [
            'key' => $key,
            'secret' => $secret,
            'region' => Config::get('aws.translate.region', 'us-west-2'),
        ];
    }

    private static function awsHost(string $region): string
    {
        return 'translate.' . $region . '.amazonaws.com';
    }

    /**
     * @return list<string>
     */
    private static function awsSignedHeaders(string $payload, array $credentials): array
    {
        $region = $credentials['region'];
        $host = self::awsHost($region);

        $now = new DateTimeImmutable('now', new DateTimeZone('UTC'));
        $amzDate = $now->format('Ymd\THis\Z');
        $dateStamp = $now->format('Ymd');

        $canonicalHeaders =
            'content-type:' . self::AWS_CONTENT_TYPE . "\n"
            . 'host:' . $host . "\n"
            . 'x-amz-date:' . $amzDate . "\n"
            . 'x-amz-target:' . self::AWS_TARGET . "\n";
        $signedHeaders = 'content-type;host;x-amz-date;x-amz-target';

        $canonicalRequest = implode("\n", [
            'POST',
            '/',
            '',
            $canonicalHeaders,
            $signedHeaders,
            hash('sha256', $payload),
        ]);

        $scope = $dateStamp . '/' . $region . '/' . self::AWS_SERVICE . '/aws4_request';
        $stringToSign = implode("\n", [
            'AWS4-HMAC-SHA256',
            $amzDate,
            $scope,
            hash('sha256', $canonicalRequest),
        ]);

        $kDate = hash_hmac('sha256', $dateStamp, 'AWS4' . $credentials['secret'], true);
        $kRegion = hash_hmac('sha256', $region, $kDate, true);
        $kService = hash_hmac('sha256', self::AWS_SERVICE, $kRegion, true);
        $kSigning = hash_hmac('sha256', 'aws4_request', $kService, true);
        $signature = hash_hmac('sha256', $stringToSign, $kSigning);

        $authorization = 'AWS4-HMAC-SHA256 '
            . 'Credential=' . $credentials['key'] . '/' . $scope . ', '
            . 'SignedHeaders=' . $signedHeaders . ', '
            . 'Signature=' . $signature;

        return [
            'Content-Type: ' . self::AWS_CONTENT_TYPE,
            'X-Amz-Date: ' . $amzDate,
            'X-Amz-Target: ' . self::AWS_TARGET,
            'Authorization: ' . $authorization,
        ];
    }

    private static function awsTranslateText(string $text, string $source, string $target, array $credentials): string
    {
        $payload = json_encode([
            'Text' => $text,
            'SourceLanguageCode' => $source,
            'TargetLanguageCode' => $target,
        ], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);

        if ($payload === false) {
            throw new RuntimeException('Failed to encode request payload.');
        }

        $host = self::awsHost($credentials['region']);
        $headers = self::awsSignedHeaders($payload, $credentials);
        $attempts = 0;
        $maxAttempts = 4;

        while (true) {
            $attempts++;

            $ch = curl_init('https://' . $host . '/');
            curl_setopt_array($ch, [
                CURLOPT_POST => true,
                CURLOPT_POSTFIELDS => $payload,
                CURLOPT_HTTPHEADER => $headers,
                CURLOPT_RETURNTRANSFER => true,
                CURLOPT_TIMEOUT => 30,
            ]);

            $response = curl_exec($ch);
            $httpCode = (int) curl_getinfo($ch, CURLINFO_HTTP_CODE);
            $curlError = curl_error($ch);
            curl_close($ch);

            if ($response === false) {
                if ($attempts < $maxAttempts) {
                    usleep(500000 * $attempts);
                    $headers = self::awsSignedHeaders($payload, $credentials);
                    continue;
                }
                throw new RuntimeException('cURL error: ' . $curlError);
            }

            if (($httpCode === 429 || $httpCode >= 500) && $attempts < $maxAttempts) {
                usleep(500000 * $attempts);
                $headers = self::awsSignedHeaders($payload, $credentials);
                continue;
            }

            $decoded = json_decode((string) $response, true);

            if ($httpCode !== 200 || !is_array($decoded) || !isset($decoded['TranslatedText'])) {
                $message = is_array($decoded)
                    ? (string) ($decoded['message'] ?? $decoded['Message'] ?? $response)
                    : (string) $response;
                throw new RuntimeException("AWS Translate HTTP {$httpCode}: {$message}");
            }

            return (string) $decoded['TranslatedText'];
        }
    }

    /**
     * @return list<string>
     */
    public static function targetLangs(): array
    {
        return array_values(array_filter(
            AdminPanel::$langs,
            fn(string $lang) => $lang !== self::SOURCE_LANG
        ));
    }

    private static function ensureLangKeys(array &$data, array $targetLangs): bool
    {
        $dataUpdated = false;

        foreach ($data as $md5 => $entry) {
            if (!is_array($entry) || !isset($entry[self::SOURCE_LANG])) {
                continue;
            }

            foreach ($targetLangs as $lang) {
                if (!isset($entry[$lang])) {
                    $data[$md5][$lang] = '';
                    $dataUpdated = true;
                }
            }
        }

        return $dataUpdated;
    }

    private static function translateEntry(array &$data, string $md5, string $targetLang, array $credentials): bool
    {
        $entry = $data[$md5] ?? null;
        if (!is_array($entry) || !self::needsTranslation($entry, $targetLang)) {
            return false;
        }

        $sourceText = (string) $entry[self::SOURCE_LANG];

        try {
            if (!self::hasCyrillic($sourceText)) {
                $data[$md5][$targetLang] = $sourceText;
            } else {
                $translated = self::awsTranslateText(
                    $sourceText,
                    self::SOURCE_LANG,
                    $targetLang,
                    $credentials
                );
                $data[$md5][$targetLang] = self::applySourceCapitalization(
                    $sourceText,
                    $translated,
                    $targetLang
                );
            }

            self::saveData($data);
            return true;
        } catch (Throwable) {
            return false;
        }
    }

    public static function fillEmptyTranslationsForLang(
        string $targetLang,
        ?array $onlyMd5 = null,
        ?int $limit = null
    ): int {
        if ($targetLang === self::SOURCE_LANG) {
            return 0;
        }

        $credentials = self::awsCredentials();
        if ($credentials === null) {
            return 0;
        }

        $data = self::currentData();
        $translated = 0;

        foreach ($data as $md5 => $entry) {
            if ($limit !== null && $translated >= $limit) {
                break;
            }

            if (!is_array($entry)) {
                continue;
            }

            if ($onlyMd5 !== null && !in_array($md5, $onlyMd5, true)) {
                continue;
            }

            if (!self::translateEntry($data, $md5, $targetLang, $credentials)) {
                continue;
            }

            $translated++;
        }

        return $translated;
    }

    public static function fillEmptyTranslations(int $limit = 10, ?array $onlyMd5 = null): int
    {
        $targetLangs = self::targetLangs();
        if ($targetLangs === []) {
            return 0;
        }

        $credentials = self::awsCredentials();
        if ($credentials === null) {
            return 0;
        }

        $data = self::currentData();
        if (self::ensureLangKeys($data, $targetLangs)) {
            self::saveData($data);
        }

        $translated = 0;

        foreach ($targetLangs as $targetLang) {
            if ($translated >= $limit) {
                break;
            }

            foreach ($data as $md5 => $entry) {
                if ($translated >= $limit) {
                    break;
                }

                if (!is_array($entry)) {
                    continue;
                }

                if ($onlyMd5 !== null && !in_array($md5, $onlyMd5, true)) {
                    continue;
                }

                if (!self::translateEntry($data, $md5, $targetLang, $credentials)) {
                    continue;
                }

                $translated++;
            }
        }

        return $translated;
    }

    public static function translateTexts(array $texts, string $targetLang = 'en'): array
    {
        // В texts всегда ждем ru тексты
        // Отдаем переводы из targetLang

        // Переводы хранятся в config/admin_localizations.json
        // В виде {md5: {ru: 'text', en: 'text'}}
        // Если текущего нет то создаем его
        // Если перевода нет под текущий язык — пробуем AWS Translate
        // Если перевода все еще нет то отдаем ▧▧▧▧

        $targetLangs = self::targetLangs();
        $data = self::currentData();
        $result = [];
        $dataUpdated = false;
        $md5List = [];

        foreach ($texts as $text) {
            $md5List[] = $text['md5'];

            if (!isset($data[$text['md5']])) {
                $dataUpdated = true;
                $data[$text['md5']] = [
                    'ru' => $text['text'],
                ];
            }

            foreach ($targetLangs as $lang) {
                if (!isset($data[$text['md5']][$lang])) {
                    $dataUpdated = true;
                    $data[$text['md5']][$lang] = '';
                }
            }
        }

        if ($dataUpdated) {
            self::saveData($data);
        }

        self::fillEmptyTranslationsForLang($targetLang, $md5List);
        $data = self::currentData();

        foreach ($texts as $text) {
            $translation = $data[$text['md5']][$targetLang] ?? '';
            $textResult = !!$translation ? $translation : '▧▧▧▧';

            $result[] = [
                'md5' => $text['md5'],
                'text' => $textResult,
            ];
        }

        return $result;
    }
}
