<?php
namespace EasyChat\Library\Messenger;

use EasyChat\Library\Http\ExternalHttpClient;
use EasyChat\Library\SafeRuntime;
use EasyChat\Model\MessageContext;

class CommerceContextService {
    private const MAX_IMAGE_BYTES = 1048576;
    private const THUMB_MAX_WIDTH = 180;
    private const THUMB_MAX_HEIGHT = 140;
    private const IMAGE_MIMES = ['image/jpeg', 'image/png', 'image/gif', 'image/webp'];

    private $registry;
    private $http;

    public function __construct($registry, ?ExternalHttpClient $http = null) {
        $this->registry = $registry;
        $this->http = $http ?: new ExternalHttpClient($registry);
    }

    public function contextsForChatRows(array $rows, string $image_route): array {
        $chat_ids = [];
        foreach ($rows as $row) {
            if (in_array(($row['source_provider'] ?? ''), ['avito', 'youla', 'wildberries', 'ozon', 'yandex_market'], true)) {
                $chat_ids[] = (int)($row['chat_id'] ?? 0);
            }
        }

        $contexts = (new MessageContext($this->registry))->getCurrentMarketplaceContextsByChatIds($chat_ids);
        $result = [];
        foreach ($rows as $row) {
            $chat_id = (int)($row['chat_id'] ?? 0);
            if ($chat_id <= 0 || empty($contexts[$chat_id])) {
                continue;
            }
            $result[$chat_id] = $this->payloadForRow($row, $contexts[$chat_id], $image_route);
        }
        return $result;
    }

    public function payloadForRow(array $row, array $context, string $image_route): array {
        $provider = strtolower((string)($context['provider'] ?? $row['source_provider'] ?? ''));
        $kind = (string)($context['context_kind'] ?? '');
        $item_id = (string)($context['item_id'] ?? '');
        $title = trim((string)($context['title'] ?? ''));
        if ($kind === 'general' && $title === '') {
            $title = $provider === 'youla' ? 'Общий запрос Юлы' : 'Общий запрос Авито';
        }

        $image_url = trim((string)($context['image_url'] ?? ''));
        $thumbnail_url = '';
        $thumbnail_version = '';
        if (in_array($provider, ['avito', 'youla', 'wildberries', 'ozon', 'yandex_market'], true) && $item_id !== '' && $image_url !== '' && $this->isAllowedImageUrl($provider, $image_url)) {
            $thumbnail_version = substr(hash('sha256', $image_url), 0, 16);
            $thumbnail_url = HTTP_SERVER . 'index.php?route=' . rawurlencode($image_route)
                . '&chat_id=' . (int)($row['chat_id'] ?? $context['chat_id'] ?? 0)
                . '&provider=' . rawurlencode($provider)
                . '&item_id=' . rawurlencode($item_id)
                . '&v=' . rawurlencode($thumbnail_version);
        }

        $group = $this->groupPayload($row, $context);

        return [
            'provider' => $provider,
            'kind' => $kind === 'general' ? 'general' : 'listing',
            'context_kind' => $kind,
            'chat_type' => (string)($context['chat_type'] ?? ''),
            'title' => $title,
            'price_string' => (string)($context['price_string'] ?? ''),
            'external_item_id' => $item_id,
            'item_id' => $item_id,
            'url' => $this->safeExternalUrl($provider, (string)($context['url'] ?? '')),
            'status_id' => (string)($context['status_id'] ?? ''),
            'thumbnail_url' => $thumbnail_url,
            'thumbnail_version' => $thumbnail_version,
            'has_thumbnail' => $thumbnail_url !== '',
            'customer_group_id' => $group['id'],
            'customer_group_label' => $group['label'],
            'external_customer_id' => $group['external_customer_id'],
            'group_fallback' => $group['fallback'],
        ];
    }

    public function loadImageForContext(string $provider, int $chat_id, string $item_id): array {
        $resolved = $this->resolveImageContext($provider, $chat_id, $item_id);
        if (empty($resolved['success'])) {
            return $resolved;
        }
        $provider = (string)$resolved['provider'];
        $chat_id = (int)$resolved['chat_id'];
        $item_id = (string)$resolved['item_id'];
        $url = (string)$resolved['url'];

        $cached = $this->loadCachedImage($provider, $chat_id, $item_id, $url);
        if (!empty($cached['success'])) {
            return $cached;
        }

        $body = '';
        $written = 0;
        $response = $this->http->request('GET', $url, [
            'return_transfer' => false,
            'follow_location' => true,
            'max_redirects' => 2,
            'connect_timeout' => 3,
            'timeout' => 8,
            'trust_profile' => $provider,
            'proxy_profile' => 'messenger_attachments',
            'headers' => ['Accept: image/avif,image/webp,image/*,*/*;q=0.8'],
            'write_function' => function ($ch, $data) use (&$body, &$written) {
                $len = strlen($data);
                $written += $len;
                if ($written > self::MAX_IMAGE_BYTES) {
                    return 0;
                }
                $body .= $data;
                return $len;
            },
        ]);

        $http_code = (int)($response['http_code'] ?? 0);
        if (empty($response['transport_ok']) || $http_code < 200 || $http_code >= 300 || $body === '') {
            return ['success' => false, 'error' => 'context_image_download_failed'];
        }

        $mime = $this->detectImageMime($body, (string)($response['content_type'] ?? ''));
        if (!in_array($mime, self::IMAGE_MIMES, true)) {
            return ['success' => false, 'error' => 'context_image_mime_denied'];
        }

        $cached = $this->writeCachedImage($provider, $chat_id, $item_id, $url, $body, $mime);
        if (!empty($cached['success'])) {
            return $cached;
        }

        return [
            'success' => true,
            'body' => $body,
            'mime' => $mime,
            'etag' => '"' . substr(hash('sha256', $url . ':' . strlen($body)), 0, 32) . '"',
        ];
    }

    public function cachedImageMetaForContext(string $provider, int $chat_id, string $item_id): array {
        $resolved = $this->resolveImageContext($provider, $chat_id, $item_id);
        if (empty($resolved['success'])) {
            return $resolved;
        }

        $cached = $this->findCachedImage(
            (string)$resolved['provider'],
            (int)$resolved['chat_id'],
            (string)$resolved['item_id'],
            (string)$resolved['url']
        );
        if (empty($cached)) {
            return ['success' => false, 'error' => 'context_image_cache_miss'];
        }

        return [
            'success' => true,
            'etag' => $this->cachedImageEtag((string)$cached['path']),
            'mime' => (string)$cached['mime'],
            'last_modified' => (int)filemtime((string)$cached['path']),
            'cached' => true,
        ];
    }

    private function resolveImageContext(string $provider, int $chat_id, string $item_id): array {
        $provider = strtolower(preg_replace('/[^a-z0-9_\-]/i', '', $provider));
        $item_id = trim($item_id);
        if (!in_array($provider, ['avito', 'youla', 'wildberries', 'ozon', 'yandex_market'], true) || $chat_id <= 0 || $item_id === '') {
            return ['success' => false, 'error' => 'context_image_bad_request'];
        }

        $contexts = (new MessageContext($this->registry))->getCurrentMarketplaceContextsByChatIds([$chat_id]);
        $context = $contexts[$chat_id] ?? [];
        if (!$context || strtolower((string)($context['provider'] ?? '')) !== $provider || (string)($context['item_id'] ?? '') !== $item_id) {
            return ['success' => false, 'error' => 'context_image_not_found'];
        }

        $url = trim((string)($context['image_url'] ?? ''));
        if (!$this->isAllowedImageUrl($provider, $url)) {
            return ['success' => false, 'error' => 'context_image_url_denied'];
        }

        return [
            'success' => true,
            'provider' => $provider,
            'chat_id' => $chat_id,
            'item_id' => $item_id,
            'url' => $url,
        ];
    }

    private function loadCachedImage(string $provider, int $chat_id, string $item_id, string $url): array {
        $cached = $this->findCachedImage($provider, $chat_id, $item_id, $url);
        if (empty($cached)) {
            return [];
        }
        $body = SafeRuntime::readFile((string)$cached['path']);
        if (!is_string($body) || $body === '') {
            return [];
        }
        $mime = $this->detectImageMime($body, '');
        if ($mime === '') {
            $mime = (string)$cached['mime'];
        }
        if (!in_array($mime, self::IMAGE_MIMES, true)) {
            return [];
        }
        return [
            'success' => true,
            'body' => $body,
            'mime' => $mime,
            'etag' => $this->cachedImageEtag((string)$cached['path']),
            'last_modified' => (int)filemtime((string)$cached['path']),
            'cached' => true,
        ];
    }

    private function writeCachedImage(string $provider, int $chat_id, string $item_id, string $url, string $body, string $mime): array {
        $dir = $this->cacheDir();
        if ($dir === '') {
            return [];
        }

        $image = $this->createImageFromString($body);
        if (!$image) {
            return $this->writeRawCachedImage($provider, $chat_id, $item_id, $url, $body, $mime);
        }

        $src_w = imagesx($image);
        $src_h = imagesy($image);
        if ($src_w <= 0 || $src_h <= 0) {
            $this->destroyImage($image);
            return $this->writeRawCachedImage($provider, $chat_id, $item_id, $url, $body, $mime);
        }

        $scale = min(1, self::THUMB_MAX_WIDTH / $src_w, self::THUMB_MAX_HEIGHT / $src_h);
        $dst_w = max(1, (int)round($src_w * $scale));
        $dst_h = max(1, (int)round($src_h * $scale));
        $canvas = imagecreatetruecolor($dst_w, $dst_h);
        if (!$canvas) {
            $this->destroyImage($image);
            return $this->writeRawCachedImage($provider, $chat_id, $item_id, $url, $body, $mime);
        }

        $use_webp = function_exists('imagewebp');
        if ($use_webp) {
            imagealphablending($canvas, false);
            imagesavealpha($canvas, true);
            $transparent = imagecolorallocatealpha($canvas, 255, 255, 255, 127);
            imagefilledrectangle($canvas, 0, 0, $dst_w, $dst_h, $transparent);
        } else {
            $bg = imagecolorallocate($canvas, 255, 255, 255);
            imagefilledrectangle($canvas, 0, 0, $dst_w, $dst_h, $bg);
        }
        imagecopyresampled($canvas, $image, 0, 0, 0, 0, $dst_w, $dst_h, $src_w, $src_h);
        $this->destroyImage($image);

        $extension = $use_webp ? 'webp' : 'jpg';
        $out_mime = $use_webp ? 'image/webp' : 'image/jpeg';
        $path = $dir . $this->cacheBase($provider, $chat_id, $item_id, $url) . '.' . $extension;
        $tmp = $path . '.tmp_' . bin2hex(random_bytes(4));
        $ok = $use_webp
            ? SafeRuntime::call(function () use ($canvas, $tmp) { return imagewebp($canvas, $tmp, 76); }, false)
            : SafeRuntime::call(function () use ($canvas, $tmp) { return imagejpeg($canvas, $tmp, 78); }, false);
        $this->destroyImage($canvas);

        if (!$ok || !SafeRuntime::rename($tmp, $path)) {
            SafeRuntime::unlink($tmp);
            return $this->writeRawCachedImage($provider, $chat_id, $item_id, $url, $body, $mime);
        }

        return $this->payloadFromCachedFile($path, $out_mime);
    }

    private function writeRawCachedImage(string $provider, int $chat_id, string $item_id, string $url, string $body, string $mime): array {
        $extension = $this->extensionForMime($mime);
        if ($extension === '') {
            return [];
        }
        $dir = $this->cacheDir();
        if ($dir === '') {
            return [];
        }
        $path = $dir . $this->cacheBase($provider, $chat_id, $item_id, $url) . '.' . $extension;
        $tmp = $path . '.tmp_' . bin2hex(random_bytes(4));
        if (!SafeRuntime::writeFile($tmp, $body) || !SafeRuntime::rename($tmp, $path)) {
            SafeRuntime::unlink($tmp);
            return [];
        }
        return $this->payloadFromCachedFile($path, $mime);
    }

    private function payloadFromCachedFile(string $path, string $mime): array {
        $body = SafeRuntime::readFile($path);
        if (!is_string($body) || $body === '') {
            return [];
        }
        return [
            'success' => true,
            'body' => $body,
            'mime' => $mime,
            'etag' => $this->cachedImageEtag($path),
            'last_modified' => (int)filemtime($path),
            'cached' => true,
        ];
    }

    private function findCachedImage(string $provider, int $chat_id, string $item_id, string $url): array {
        $dir = $this->cacheDir();
        if ($dir === '') {
            return [];
        }
        $base = $this->cacheBase($provider, $chat_id, $item_id, $url);
        foreach (['webp', 'jpg', 'jpeg', 'png', 'gif'] as $extension) {
            $path = $dir . $base . '.' . $extension;
            if (!is_file($path)) {
                continue;
            }
            return [
                'path' => $path,
                'extension' => $extension,
                'mime' => $this->mimeForExtension($extension),
            ];
        }
        return [];
    }

    private function cachedImageEtag(string $path): string {
        return '"' . substr(hash('sha256', basename($path) . ':' . (string)filemtime($path) . ':' . (string)filesize($path)), 0, 32) . '"';
    }

    private function cacheBase(string $provider, int $chat_id, string $item_id, string $url): string {
        $provider = preg_replace('/[^a-z0-9_\-]/i', '', strtolower($provider));
        $item = preg_replace('/[^a-z0-9_\-]/i', '', strtolower($item_id));
        $hash = substr(hash('sha256', $provider . '|' . $chat_id . '|' . $item_id . '|' . $url), 0, 24);
        return 'cc_' . ($provider ?: 'provider') . '_' . (int)$chat_id . '_' . ($item ?: 'item') . '_' . $hash;
    }

    private function cacheDir(): string {
        $dir = rtrim(DIR_IMAGE, DS) . DS . 'commerce_context' . DS;
        if (!SafeRuntime::mkdir($dir, 0755, true)) {
            return '';
        }
        $deny = "Options -Indexes\n" .
            "<IfModule mod_authz_core.c>\n" .
            "    Require all denied\n" .
            "</IfModule>\n" .
            "<IfModule !mod_authz_core.c>\n" .
            "    Deny from all\n" .
            "</IfModule>\n";
        SafeRuntime::writeFile($dir . '.htaccess', $deny);
        return $dir;
    }

    private function groupPayload(array $row, array $context): array {
        $provider = strtolower((string)($row['source_provider'] ?? $context['provider'] ?? ''));
        $metadata = json_decode((string)($row['contact_metadata_json'] ?? ''), true);
        $metadata = is_array($metadata) ? $metadata : [];
        $external_customer_id = $this->normalizeExternalCustomerId(
            $provider,
            (string)($metadata['counterparty_author_id'] ?? $metadata['author_id'] ?? $metadata['buyer_user_id'] ?? '')
        );
        $fallback = false;
        if ($external_customer_id === '') {
            $fallback = true;
            $external_customer_id = 'chat_' . (int)($row['chat_id'] ?? 0);
        }

        $channel_id = (int)($row['source_channel_id'] ?? $context['channel_id'] ?? 0);
        $label = trim((string)($row['customer_name'] ?? ''));
        if ($label === '') {
            $label = [
                'youla' => 'Покупатель Юлы',
                'wildberries' => 'Покупатель Wildberries',
                'ozon' => 'Покупатель Ozon',
            ][$provider] ?? 'Покупатель Avito';
        }

        return [
            'id' => $provider . ':' . $channel_id . ':' . $external_customer_id,
            'label' => $label,
            'external_customer_id' => $fallback ? '' : $external_customer_id,
            'fallback' => $fallback ? 1 : 0,
        ];
    }

    private function safeExternalUrl(string $provider, string $url): string {
        $url = trim($url);
        if ($url === '') {
            return '';
        }
        $parts = parse_url($url);
        if (!is_array($parts) || strtolower((string)($parts['scheme'] ?? '')) !== 'https') {
            return '';
        }
        $host = strtolower((string)($parts['host'] ?? ''));
        if ($provider === 'avito' && !$this->hostMatches($host, ['avito.ru', 'www.avito.ru'])) {
            return '';
        }
        if ($provider === 'youla' && !$this->hostMatches($host, ['youla.ru', 'www.youla.ru', 'youla.io', 'youla.link'])) {
            return '';
        }
        if ($provider === 'wildberries' && !$this->hostMatches($host, ['wildberries.ru', 'www.wildberries.ru'])) {
            return '';
        }
        if ($provider === 'ozon' && !$this->hostMatches($host, ['ozon.ru', 'www.ozon.ru'])) {
            return '';
        }
        if ($provider === 'yandex_market' && !$this->hostMatches($host, ['market.yandex.ru'])) {
            return '';
        }
        return $url;
    }

    private function normalizeExternalCustomerId(string $provider, string $value): string {
        $value = trim($value);
        if ($value === '') {
            return '';
        }
        if (in_array($provider, ['youla', 'wildberries', 'ozon', 'yandex_market'], true)) {
            return preg_replace('/[^a-z0-9_\-:]+/i', '', $value);
        }
        return preg_replace('/\D+/', '', $value);
    }

    private function isAllowedImageUrl(string $provider, string $url): bool {
        if ($url === '') {
            return false;
        }
        $parts = parse_url($url);
        if (!is_array($parts) || strtolower((string)($parts['scheme'] ?? '')) !== 'https') {
            return false;
        }
        $host = strtolower((string)($parts['host'] ?? ''));
        if ($provider === 'avito') {
            return $this->hostMatches($host, ['avito.st', 'avito.ru']);
        }
        if ($provider === 'youla') {
            return $this->hostMatches($host, ['youla.ru', 'youla.io', 'youla.link', 'avatars.mds.yandex.net']);
        }
        if ($provider === 'wildberries') {
            return $this->hostMatches($host, ['wbbasket.ru', 'wildberries.ru']);
        }
        if ($provider === 'ozon') {
            return $this->hostMatches($host, ['ozon.ru', 'ozonusercontent.com', 'ozone.ru']);
        }
        if ($provider === 'yandex_market') {
            return $this->hostMatches($host, ['yandex.ru', 'yandex.net', 'yastatic.net']);
        }
        return false;
    }

    private function hostMatches(string $host, array $allowed): bool {
        foreach ($allowed as $allowed_host) {
            $allowed_host = strtolower($allowed_host);
            if ($host === $allowed_host || substr($host, -strlen('.' . $allowed_host)) === '.' . $allowed_host) {
                return true;
            }
        }
        return false;
    }

    private function detectImageMime(string $body, string $content_type): string {
        $mime = '';
        if (class_exists('\finfo')) {
            $finfo = new \finfo(FILEINFO_MIME_TYPE);
            $mime = (string)$finfo->buffer($body);
        }
        if ($mime === '' || $mime === 'application/octet-stream') {
            $mime = strtolower(trim(explode(';', $content_type)[0]));
        }
        if ($mime === 'image/jpg') {
            $mime = 'image/jpeg';
        }
        return $mime;
    }

    private function createImageFromString(string $body) {
        if (!function_exists('imagecreatefromstring')) {
            return false;
        }
        return SafeRuntime::call(function () use ($body) {
            return imagecreatefromstring($body);
        }, false);
    }

    private function destroyImage($image): void {
        if (function_exists('imagedestroy') && defined('PHP_VERSION_ID') && PHP_VERSION_ID < 80500) {
            SafeRuntime::call(function () use ($image) {
                imagedestroy($image);
                return true;
            }, false);
        }
    }

    private function extensionForMime(string $mime): string {
        switch (strtolower($mime)) {
            case 'image/webp':
                return 'webp';
            case 'image/jpeg':
                return 'jpg';
            case 'image/png':
                return 'png';
            case 'image/gif':
                return 'gif';
        }
        return '';
    }

    private function mimeForExtension(string $extension): string {
        switch (strtolower($extension)) {
            case 'webp':
                return 'image/webp';
            case 'jpg':
            case 'jpeg':
                return 'image/jpeg';
            case 'png':
                return 'image/png';
            case 'gif':
                return 'image/gif';
        }
        return '';
    }
}
