<?php
declare(strict_types=1);

namespace KbMarkdownImporter\Olm;

use KbMarkdownImporter\Import\ImportLogger;
use KbMarkdownImporter\Plugin;

final class ChangelogSync
{
    private const OPTION_ITEMS = 'kb_markdown_importer_product_updates';
    private const OPTION_LAST_SYNC = 'kb_markdown_importer_changelog_last_sync';

    private array $settings;
    private string $baseUrl;
    private array $headers = [];

    public function __construct(?array $settings = null)
    {
        $this->settings = $settings ?: Plugin::settings();
        $this->baseUrl = self::normalizeBaseUrl((string) ($this->settings['olm_base_url'] ?? ''));
    }

    public static function items(): array
    {
        $items = get_option(self::OPTION_ITEMS, []);

        return is_array($items) ? $items : [];
    }

    public static function lastSync(): string
    {
        return (string) get_option(self::OPTION_LAST_SYNC, '');
    }

    public static function normalizeBaseUrl(string $url): string
    {
        $url = trim($url);

        if ('' === $url) {
            return '';
        }

        if (! preg_match('#^https?://#i', $url)) {
            $url = 'https://' . $url;
        }

        return rtrim($url, '/');
    }

    public function sync(): \WP_REST_Response
    {
        ImportLogger::info('OLM changelog synchronization started.');

        $token = $this->login();
        if (is_wp_error($token)) {
            ImportLogger::error('OLM changelog login failed: ' . $token->get_error_message());
            return new \WP_REST_Response(['success' => false, 'message' => $token->get_error_message()], 500);
        }

        $this->headers = [
            'Content-Type' => 'application/json',
            'Accept' => 'application/json',
            'Authorization' => 'Bearer ' . $token,
        ];

        $downloadIds = $this->productDownloadIds();
        if (is_wp_error($downloadIds)) {
            ImportLogger::error('OLM product lookup failed: ' . $downloadIds->get_error_message());
            return new \WP_REST_Response(['success' => false, 'message' => $downloadIds->get_error_message()], 500);
        }

        $items = [];

        foreach ($downloadIds as $downloadId) {
            $versions = $this->downloadFieldVersions($downloadId);

            if (is_wp_error($versions)) {
                ImportLogger::warning('OLM download field lookup failed for ' . $downloadId . ': ' . $versions->get_error_message());
                continue;
            }

            foreach ($versions as $version) {
                $item = $this->normalizeVersion($version);

                if (null !== $item) {
                    $items[] = $item;
                }
            }
        }

        usort($items, static fn (array $a, array $b): int => ($b['_timestamp'] ?? 0) <=> ($a['_timestamp'] ?? 0));
        $items = array_filter($items, fn (array $item): bool => $this->isInDateWindow($item));
        $items = array_map(static function (array $item): array {
            unset($item['_timestamp']);
            return $item;
        }, array_values($items));

        update_option(self::OPTION_ITEMS, $items, false);
        update_option(self::OPTION_LAST_SYNC, current_time('mysql'), false);
        ImportLogger::info('OLM changelog synchronization completed. Entries: ' . count($items));

        return new \WP_REST_Response([
            'success' => true,
            'stats' => [
                'downloads' => count($downloadIds),
                'updates' => count($items),
            ],
        ]);
    }

    private function login(): string|\WP_Error
    {
        if ('' === $this->baseUrl) {
            return new \WP_Error('kb_olm_missing_base_url', __('OLM base URL missing.', 'kb-markdown-importer'));
        }

        $username = trim((string) ($this->settings['olm_username'] ?? ''));
        $password = (string) ($this->settings['olm_password'] ?? '');

        if ('' === $username || '' === $password) {
            return new \WP_Error('kb_olm_missing_credentials', __('OLM credentials missing.', 'kb-markdown-importer'));
        }

        $response = wp_remote_post($this->baseUrl . '/login', [
            'timeout' => 12,
            'redirection' => 3,
            'headers' => [
                'Content-Type' => 'application/json',
                'Accept' => 'application/json',
            ],
            'body' => wp_json_encode([
                'username' => $username,
                'password' => $password,
            ]),
        ]);

        if (is_wp_error($response)) {
            return $response;
        }

        $status = (int) wp_remote_retrieve_response_code($response);
        $body = (string) wp_remote_retrieve_body($response);
        $data = json_decode($body, true);

        if ($status < 200 || $status >= 300 || ! is_array($data) || empty($data['bearerToken'])) {
            return new \WP_Error(
                'kb_olm_login_failed',
                __('OLM login failed.', 'kb-markdown-importer'),
                [
                    'status' => $status,
                    'response_excerpt' => substr(wp_strip_all_tags($body), 0, 1000),
                ]
            );
        }

        return (string) $data['bearerToken'];
    }

    private function productDownloadIds(): array|\WP_Error
    {
        $ids = [];
        $page = 1;

        while (true) {
            $data = $this->getJson($this->baseUrl . '/api/rest/v1/product?page=' . $page . '&size=1');

            if (is_wp_error($data)) {
                return $data;
            }

            $products = is_array($data['content'] ?? null) ? $data['content'] : [];
            if (! $products) {
                break;
            }

            foreach ($products as $product) {
                if (! is_array($product)) {
                    continue;
                }

                foreach ((array) ($product['downloads'] ?? []) as $download) {
                    $id = is_array($download) ? (string) ($download['id'] ?? '') : '';

                    if ('' !== $id) {
                        $ids[$id] = $id;
                    }
                }
            }

            $page++;
        }

        return array_values($ids);
    }

    private function downloadFieldVersions(string $downloadId): array|\WP_Error
    {
        $versions = [];
        $page = 1;

        while (true) {
            $data = $this->getJson($this->baseUrl . '/api/rest/v1/download/field/' . rawurlencode($downloadId) . '?page=' . $page . '&size=1');

            if (is_wp_error($data)) {
                return $data;
            }

            $content = is_array($data['content'] ?? null) ? $data['content'] : [];
            if (! $content) {
                break;
            }

            foreach ($content as $version) {
                if (is_array($version)) {
                    $versions[] = $version;
                }
            }

            $page++;
        }

        return $versions;
    }

    private function getJson(string $url): array|\WP_Error
    {
        $response = wp_remote_get($url, [
            'timeout' => 12,
            'redirection' => 3,
            'headers' => $this->headers,
            'user-agent' => 'KB Markdown Importer/' . KB_MARKDOWN_IMPORTER_VERSION,
        ]);

        if (is_wp_error($response)) {
            return $response;
        }

        $status = (int) wp_remote_retrieve_response_code($response);
        $body = (string) wp_remote_retrieve_body($response);
        $data = json_decode($body, true);

        if ($status < 200 || $status >= 300 || ! is_array($data)) {
            return new \WP_Error(
                'kb_olm_request_failed',
                __('OLM request failed.', 'kb-markdown-importer'),
                [
                    'status' => $status,
                    'url' => $url,
                    'response_excerpt' => substr(wp_strip_all_tags($body), 0, 1000),
                ]
            );
        }

        return $data;
    }

    private function normalizeVersion(array $version): ?array
    {
        $productVersion = is_array($version['productVersion'] ?? null) ? $version['productVersion'] : [];
        $product = is_array($productVersion['product'] ?? null) ? $productVersion['product'] : [];
        $downloadField = is_array($version['downloadField'] ?? null) ? $version['downloadField'] : [];
        $productNo = strtolower((string) ($product['productNo'] ?? ''));
        $publishedAt = (string) ($version['publishedAt'] ?? '');

        if (true !== ($version['qa'] ?? false) || true !== ($product['published'] ?? false) || '' === $publishedAt) {
            return null;
        }

        if (in_array($productNo, $this->ignoredOlmNumbers(), true)) {
            return null;
        }

        $timestamp = $this->dateTimestamp($publishedAt);
        $productName = $this->cleanText((string) ($product['name'] ?? ''));
        $downloadName = $this->cleanText((string) ($downloadField['name'] ?? ''));

        if (true !== ($downloadField['starfaceModule'] ?? false) && '' !== $downloadName) {
            $productName = trim($productName . ' - ' . $downloadName);
        }

        $changelogLines = $this->changelogLines((string) ($version['changelog'] ?? ''));

        return [
            'product' => $productName,
            'version' => $this->moduleVersion($productVersion, $version),
            'date' => $this->formatDate($publishedAt),
            'month_label' => $timestamp > 0 ? wp_date('F Y', $timestamp) : '',
            'changelog' => implode(' ', $changelogLines),
            'changelog_lines' => $changelogLines,
            'starface_min' => $this->starfaceVersion(is_array($productVersion['minStarfaceVersion'] ?? null) ? $productVersion['minStarfaceVersion'] : []),
            'starface_max' => $this->starfaceVersion(is_array($productVersion['maxStarfaceVersion'] ?? null) ? $productVersion['maxStarfaceVersion'] : []),
            'link' => $this->validUrl((string) ($product['productPageURI'] ?? '')),
            'download_link' => '' !== $productNo ? 'https://get.o-byte.com?olm=' . rawurlencode($productNo) : '',
            'product_no' => $productNo,
            'published_at' => $timestamp > 0 ? wp_date('Y-m-d', $timestamp) : '',
            '_timestamp' => $timestamp,
        ];
    }

    private function isInDateWindow(array $item): bool
    {
        $timestamp = (int) ($item['_timestamp'] ?? 0);
        if ($timestamp <= 0) {
            return false;
        }

        $months = max(1, min(24, (int) ($this->settings['product_updates_olm_months'] ?? 4)));
        $timezone = wp_timezone();
        $date = (new \DateTimeImmutable('@' . $timestamp))->setTimezone($timezone);
        $now = new \DateTimeImmutable('now', $timezone);
        $start = $now->modify('first day of this month')->modify('-' . $months . ' months')->setTime(0, 0, 0);

        return $date >= $start && $date <= $now;
    }

    private function ignoredOlmNumbers(): array
    {
        $value = strtolower((string) ($this->settings['product_updates_olm_ignore_numbers'] ?? ''));

        return array_values(array_filter(array_map('trim', explode(',', $value)), static fn (string $item): bool => '' !== $item));
    }

    private function moduleVersion(array $productVersion, array $downloadVersion): string
    {
        return implode('.', [
            (string) ($productVersion['major'] ?? ''),
            (string) ($productVersion['minor'] ?? ''),
            (string) ($downloadVersion['bugfixVersion'] ?? ''),
        ]);
    }

    private function starfaceVersion(array $version): string
    {
        return implode('.', [
            (string) ($version['major'] ?? ''),
            (string) ($version['minor'] ?? ''),
            (string) ($version['build'] ?? ''),
            (string) ($version['revision'] ?? ''),
        ]);
    }

    private function changelogLines(string $value): array
    {
        $value = str_replace(["\r\n", "\r"], "\n", $value);
        $lines = [];

        foreach (explode("\n", $value) as $line) {
            $line = $this->cleanText($line);

            if ('' !== $line) {
                $lines[] = $line;
            }
        }

        return $lines;
    }

    private function cleanText(string $value): string
    {
        $value = html_entity_decode($value, ENT_QUOTES | ENT_HTML5, get_bloginfo('charset'));
        $value = preg_replace('/<br\s*\/?>/i', ' ', $value) ?? $value;
        $value = wp_strip_all_tags($value);
        $value = preg_replace('/\s+/', ' ', $value) ?? $value;

        return trim($value);
    }

    private function validUrl(string $value): string
    {
        $value = trim($value);

        if ('' === $value || in_array($value, ['.', '-'], true)) {
            return '';
        }

        return filter_var($value, FILTER_VALIDATE_URL) ? $value : '';
    }

    private function dateTimestamp(string $date): int
    {
        if ('' === $date) {
            return 0;
        }

        $timestamp = strtotime($date);

        return $timestamp ?: 0;
    }

    private function formatDate(string $date): string
    {
        $timestamp = $this->dateTimestamp($date);

        return $timestamp > 0 ? wp_date((string) get_option('date_format'), $timestamp) : $date;
    }
}
