<?php

declare(strict_types=1);

final class MyronDashboardTipsFeedService
{
    private const DEFAULT_FEED_URL = 'https://myroncms.com/feed/dashboard-tips.xml';
    private const MIN_REFRESH_SECONDS = 900;

    public function __construct(private readonly MyronDashboardTipsFeedRepository $repository)
    {
    }

    /**
     * @return list<array<string, mixed>>
     */
    public function tipsForContext(string $context, string $plan, int $limit = 3): array
    {
        $this->refreshIfDue();
        $tips = $this->repository->listTipsForContext($context, $limit);
        if ($tips !== []) {
            return $tips;
        }

        return array_slice($this->fallbackTips($context, $plan), 0, max(1, $limit));
    }

    public function refreshIfDue(): void
    {
        $feedUrl = $this->feedUrl();
        if ($feedUrl === '') {
            return;
        }

        $state = $this->repository->cacheState($feedUrl);
        $lastChecked = strtotime((string) ($state['last_checked_at'] ?? ''));
        if ($lastChecked !== false && (time() - $lastChecked) < self::MIN_REFRESH_SECONDS) {
            return;
        }

        try {
            $response = $this->fetchFeed($feedUrl, (string) ($state['etag'] ?? ''), (string) ($state['last_modified'] ?? ''));
            if ($response['status'] === 304) {
                $this->repository->saveCacheState($feedUrl, $response['etag'], $response['lastModified']);
                return;
            }
            if ($response['status'] < 200 || $response['status'] >= 300 || trim($response['body']) === '') {
                $this->repository->saveCacheState($feedUrl, (string) ($state['etag'] ?? ''), (string) ($state['last_modified'] ?? ''));
                return;
            }

            $items = $this->parseFeed($response['body']);
            if ($items !== []) {
                $this->repository->replaceItems($items);
            }
            $this->repository->saveCacheState($feedUrl, $response['etag'], $response['lastModified']);
        } catch (Throwable) {
            $this->repository->saveCacheState($feedUrl, (string) ($state['etag'] ?? ''), (string) ($state['last_modified'] ?? ''));
        }
    }

    private function feedUrl(): string
    {
        $url = trim((string) (getenv('MYRON_DASHBOARD_TIPS_FEED_URL') ?: self::DEFAULT_FEED_URL));
        return filter_var($url, FILTER_VALIDATE_URL) ? $url : '';
    }

    /**
     * @return array{status:int,body:string,etag:string,lastModified:string}
     */
    private function fetchFeed(string $feedUrl, string $etag, string $lastModified): array
    {
        $headers = ['User-Agent: MyronCMS Dashboard Tips/1.0'];
        if ($etag !== '') {
            $headers[] = 'If-None-Match: ' . $etag;
        }
        if ($lastModified !== '') {
            $headers[] = 'If-Modified-Since: ' . $lastModified;
        }

        $context = stream_context_create([
            'http' => [
                'method' => 'GET',
                'timeout' => 2,
                'ignore_errors' => true,
                'header' => implode("\r\n", $headers),
            ],
        ]);
        $body = @file_get_contents($feedUrl, false, $context);
        $responseHeaders = function_exists('http_get_last_response_headers') ? http_get_last_response_headers() : [];
        $responseHeaders = is_array($responseHeaders) ? $responseHeaders : [];

        return [
            'status' => $this->statusFromHeaders($responseHeaders),
            'body' => is_string($body) ? $body : '',
            'etag' => $this->headerValue($responseHeaders, 'etag') ?: $etag,
            'lastModified' => $this->headerValue($responseHeaders, 'last-modified') ?: $lastModified,
        ];
    }

    /**
     * @return list<array{title:string,url:string,summary:string,categories:list<string>,publishedAt:string}>
     */
    private function parseFeed(string $xml): array
    {
        if (!function_exists('simplexml_load_string')) {
            return [];
        }

        $previous = libxml_use_internal_errors(true);
        $feed = simplexml_load_string($xml);
        libxml_clear_errors();
        libxml_use_internal_errors($previous);
        if (!$feed instanceof SimpleXMLElement) {
            return [];
        }

        $nodes = [];
        if (isset($feed->channel->item)) {
            $nodes = iterator_to_array($feed->channel->item);
        } elseif (isset($feed->entry)) {
            $nodes = iterator_to_array($feed->entry);
        }

        $items = [];
        foreach ($nodes as $node) {
            if (!$node instanceof SimpleXMLElement) {
                continue;
            }
            $title = trim((string) ($node->title ?? ''));
            if ($title === '') {
                continue;
            }

            $items[] = [
                'title' => $title,
                'url' => $this->nodeUrl($node),
                'summary' => trim(strip_tags((string) (($node->description ?? '') ?: ($node->summary ?? '')))),
                'categories' => $this->nodeCategories($node),
                'publishedAt' => trim((string) (($node->pubDate ?? '') ?: ($node->updated ?? '') ?: ($node->published ?? ''))),
            ];
        }

        return $items;
    }

    /**
     * @return list<string>
     */
    private function nodeCategories(SimpleXMLElement $node): array
    {
        $categories = [];
        foreach ($node->category ?? [] as $category) {
            $value = trim((string) $category);
            if ($value === '') {
                $attributes = $category->attributes();
                $value = trim((string) ($attributes['term'] ?? ''));
            }
            if ($value !== '') {
                $categories[] = strtolower($value);
            }
        }

        return array_values(array_unique($categories));
    }

    private function nodeUrl(SimpleXMLElement $node): string
    {
        $link = trim((string) ($node->link ?? ''));
        if ($link !== '') {
            return $link;
        }

        foreach ($node->link ?? [] as $linkNode) {
            $attributes = $linkNode instanceof SimpleXMLElement ? $linkNode->attributes() : null;
            $href = trim((string) ($attributes['href'] ?? ''));
            if ($href !== '') {
                return $href;
            }
        }

        return '';
    }

    /**
     * @param list<string> $headers
     */
    private function statusFromHeaders(array $headers): int
    {
        $line = (string) ($headers[0] ?? '');
        return preg_match('/\s(\d{3})\s/', $line, $matches) === 1 ? (int) $matches[1] : 0;
    }

    /**
     * @param list<string> $headers
     */
    private function headerValue(array $headers, string $name): string
    {
        $prefix = strtolower($name) . ':';
        foreach ($headers as $header) {
            $header = trim($header);
            if (str_starts_with(strtolower($header), $prefix)) {
                return trim(substr($header, strlen($prefix)));
            }
        }

        return '';
    }

    /**
     * @return list<array<string, mixed>>
     */
    private function fallbackTips(string $context, string $plan): array
    {
        $context = strtolower(trim($context));
        $plan = MyronEntitlementSnapshot::normalizePlan($plan);
        $advancedPlan = in_array($plan, ['pro', 'agency'], true);

        $byContext = [
            'administration' => [
                ['title' => 'Review license and update status weekly', 'summary' => 'Keep the license, mail, and update surfaces current before handing the site to another operator.', 'isSecurity' => false],
                ['title' => 'Create scoped AI connections', 'summary' => $advancedPlan ? 'Use narrow scopes for each AI client and rotate tokens when an operator changes devices.' : 'Core sites can still prepare MCP access patterns with read-only scopes.', 'isSecurity' => true],
            ],
            'content' => [
                ['title' => 'Keep drafts close to publishable', 'summary' => 'Use recent page activity to move stale drafts toward review, publish, or archive decisions.', 'isSecurity' => false],
                ['title' => 'Attach reusable assets intentionally', 'summary' => 'Prefer titled assets with useful alt text so the library remains searchable later.', 'isSecurity' => false],
            ],
            'design' => [
                ['title' => 'Audit template coverage before large edits', 'summary' => 'Check which templates carry the most pages before changing shared sections or token overrides.', 'isSecurity' => false],
                ['title' => 'Keep token overrides deliberate', 'summary' => 'Small, named token changes are easier to reason about than broad per-section CSS exceptions.', 'isSecurity' => false],
            ],
        ];

        return $byContext[$context] ?? $byContext['administration'];
    }
}
