<?php

declare(strict_types=1);

namespace Plugins\Neatstudio\LinkTracker\Services;

use Illuminate\Database\UniqueConstraintViolationException;
use Illuminate\Support\Facades\Cache;
use Plugins\Neatstudio\LinkTracker\Models\Redirect;

/**
 * 外链中转：把站外链接改写为 /go/{hash}，用于点击统计、二次拦截与未来广告位。
 * 站内链接、相对路径、mailto/tel/锚点不中转。
 */
class UrlRedirector
{
    public function goUrl(string $url): string
    {
        $url = trim((string) $url);

        if (! $this->isExternal($url)) {
            return $url;
        }

        return route('go.show', $this->hashFor($url));
    }

    public function isExternal(string $url): bool
    {
        if (! preg_match('#^https?://#i', $url)) {
            return false;
        }

        $host = parse_url($url, PHP_URL_HOST);

        if (! $host) {
            return false;
        }

        $internalHost = parse_url((string) config('app.url'), PHP_URL_HOST);

        return strcasecmp($host, (string) $internalHost) !== 0;
    }

    /**
     * 取 URL 对应的短 hash（确定性：同一 URL 复用同一条记录，并发安全）。
     */
    public function hashFor(string $url): string
    {
        return Cache::remember('go.hash.'.md5($url), now()->addDay(), function () use ($url) {
            $hash = substr(hash('sha256', $url), 0, 10);

            for ($i = 0; $i < 3; $i++) {
                try {
                    Redirect::create(['hash' => $hash, 'target_url' => $url]);

                    return $hash;
                } catch (UniqueConstraintViolationException) {
                    $existing = Redirect::where('target_url', $url)->first();

                    if ($existing) {
                        return $existing->hash;
                    }

                    $hash = substr(hash('sha256', $url.$i), 0, 10);
                }
            }

            return $hash;
        });
    }

    /**
     * 把 HTML 里的站外 <a href> 改写为 /go 中转地址（站内/相对/锚点/mailto 不变）。
     */
    public function rewriteExternalLinks(string $html): string
    {
        return preg_replace_callback(
            '/<a\s+([^>]*?href=["\'])([^"\']+)(["\'][^>]*)>(.*?)<\/a>/is',
            function (array $m): string {
                $url = html_entity_decode(trim($m[2]));
                $go = $this->goUrl($url);

                if ($go === $url) {
                    return $m[0];
                }

                return '<a '.$m[1].e($go).$m[3].'>'.$m[4].'</a>';
            },
            $html
        );
    }
}
