<?php
/**
 * ThinkPHP 8 授权中间件示例（薄客户端版）
 *
 * 核心原则：
 * - 客户端只发送 domain、authtoken、authip。
 * - 授权规则全部由授权站 API 统一判断。
 * - 客户端不实现泛域名匹配、IP 绑定、状态判断、离线宽限等验证模块。
 *
 * 建议结构：
 * 1. 保存为 app/middleware/CheckLicense.php。
 * 2. 在 middleware.php 或路由中注册中间件。
 * 3. 新增 config/license.php：
 *
 * return [
 *     'api_url' => 'https://gd.php13.cn/api/',
 *     'auth_ip' => '',
 * ];
 */

namespace app\middleware;

use Closure;
use think\Request;
use think\Response;

class CheckLicense
{
    private const DEFAULT_API_URL = 'https://gd.php13.cn/api/';
    private const CONNECT_TIMEOUT = 3;
    private const TIMEOUT = 8;

    public function handle(Request $request, Closure $next): Response
    {
        if ($this->shouldSkip($request)) {
            return $next($request);
        }

        $token = $this->getAuthToken();
        if ($token === null) {
            return $this->tokenInputResponse($request);
        }

        $result = $this->requestLicenseServer([
            'domain' => $this->clientDomain($request),
            'authtoken' => $token,
            'authip' => $this->clientAuthIp(),
        ]);

        $code = (int)($result['code'] ?? 500);
        if ($code === 200) {
            return $next($request);
        }

        if ($code === 401) {
            return $this->tokenInputResponse($request, (string)($result['msg'] ?? 'AuthToken 无效'));
        }

        return $this->errorResponse('授权验证失败：' . (string)($result['msg'] ?? '未知错误'), 403);
    }

    private function shouldSkip(Request $request): bool
    {
        $path = trim($request->pathinfo(), '/');

        return $path === ''
            || str_starts_with($path, 'install')
            || str_starts_with($path, 'static')
            || str_starts_with($path, 'favicon.ico');
    }

    private function apiUrl(): string
    {
        return rtrim((string)config('license.api_url', self::DEFAULT_API_URL), '/') . '/';
    }

    private function tokenFile(): string
    {
        return app()->getRuntimePath() . 'license/token.json';
    }

    private function clientDomain(Request $request): string
    {
        $host = strtolower(trim($request->host()));
        $host = preg_replace('/:\d+$/', '', $host);

        return $host !== '' ? $host : 'unknown-domain';
    }

    private function clientAuthIp(): string
    {
        $configured = (string)config('license.auth_ip', '');
        if ($configured !== '') {
            return $configured;
        }

        return (string)($_SERVER['SERVER_ADDR'] ?? '');
    }

    private function validToken(string $token): bool
    {
        return preg_match('/^[A-Za-z0-9]{16,128}$/', $token) === 1;
    }

    private function getAuthToken(): ?string
    {
        $data = $this->readJson($this->tokenFile());
        $token = trim((string)($data['authtoken'] ?? ''));

        return $this->validToken($token) ? $token : null;
    }

    private function tokenInputResponse(Request $request, string $error = ''): Response
    {
        if ($request->isPost()) {
            $token = trim((string)$request->post('authtoken', ''));
            if (!$this->validToken($token)) {
                $error = '请输入有效的 AuthToken';
            } elseif ($this->writeJson($this->tokenFile(), ['authtoken' => $token])) {
                return redirect((string)$request->url());
            } else {
                $error = '无法写入授权文件，请检查 runtime/license 目录权限';
            }
        }

        $html = '<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>请输入授权 Token</title>
    <style>
        body { margin: 0; padding: 50px; background: #f5f5f5; font-family: Arial, sans-serif; }
        .container { max-width: 500px; margin: 0 auto; background: #fff; padding: 30px; border-radius: 8px; box-shadow: 0 2px 10px #ddd; }
        h2 { text-align: center; color: #333; margin-bottom: 20px; }
        .error { color: #dc3545; text-align: center; padding: 10px; background: #f8d7da; border-radius: 4px; margin-bottom: 20px; }
        input[type="text"] { box-sizing: border-box; width: 100%; padding: 12px; margin-bottom: 20px; border: 1px solid #ddd; border-radius: 4px; font-size: 16px; }
        button { width: 100%; padding: 12px; background: #007bff; color: white; border: 0; border-radius: 4px; font-size: 16px; cursor: pointer; }
        button:hover { background: #0056b3; }
        .tip { text-align: center; color: #666; margin-top: 20px; font-size: 14px; }
    </style>
</head>
<body>
    <div class="container">
        <h2>授权验证</h2>
        ' . ($error !== '' ? '<div class="error">' . $this->e($error) . '</div>' : '') . '
        <form method="post">
            <input type="text" name="authtoken" placeholder="请输入你的 AuthToken" required>
            <button type="submit">保存并验证</button>
        </form>
        <div class="tip">客户端只保存 Token；授权规则由授权站统一判断。</div>
    </div>
</body>
</html>';

        return Response::create($html, 'html');
    }

    private function requestLicenseServer(array $payload): array
    {
        $ch = curl_init($this->apiUrl());
        if ($ch === false) {
            return ['code' => 500, 'msg' => '初始化授权请求失败'];
        }

        curl_setopt_array($ch, [
            CURLOPT_POST => true,
            CURLOPT_POSTFIELDS => http_build_query($payload),
            CURLOPT_RETURNTRANSFER => true,
            CURLOPT_CONNECTTIMEOUT => self::CONNECT_TIMEOUT,
            CURLOPT_TIMEOUT => self::TIMEOUT,
            CURLOPT_SSL_VERIFYPEER => true,
            CURLOPT_SSL_VERIFYHOST => 2,
            CURLOPT_HTTPHEADER => ['Content-Type: application/x-www-form-urlencoded'],
        ]);

        $body = curl_exec($ch);
        $httpCode = (int)curl_getinfo($ch, CURLINFO_HTTP_CODE);
        $error = curl_error($ch);
        curl_close($ch);

        if ($body === false || $httpCode < 200 || $httpCode >= 300) {
            return ['code' => 500, 'msg' => '连接授权服务器失败' . ($error ? '：' . $error : '')];
        }

        $result = json_decode($body, true);
        if (!is_array($result)) {
            return ['code' => 500, 'msg' => '授权服务器响应格式错误'];
        }

        return $result;
    }

    private function readJson(string $file): ?array
    {
        if (!is_file($file)) {
            return null;
        }

        $data = json_decode((string)file_get_contents($file), true);
        return is_array($data) ? $data : null;
    }

    private function writeJson(string $file, array $data): bool
    {
        $dir = dirname($file);
        if (!is_dir($dir) && !mkdir($dir, 0755, true) && !is_dir($dir)) {
            return false;
        }

        $tmp = tempnam($dir, 'license_');
        if ($tmp === false) {
            return false;
        }

        $json = json_encode($data, JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT);
        $ok = file_put_contents($tmp, $json, LOCK_EX) !== false && rename($tmp, $file);
        if (!$ok && is_file($tmp)) {
            @unlink($tmp);
        }

        @chmod($file, 0600);
        return $ok;
    }

    private function errorResponse(string $message, int $status = 403): Response
    {
        $html = '<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>授权错误</title>
    <style>
        body { margin: 0; padding: 0; background: #fff; font-family: Arial, sans-serif; }
        .container { text-align: center; padding: 150px 20px; }
        .error { color: #d93025; font-size: 20px; }
    </style>
</head>
<body>
    <div class="container">
        <div class="error">' . $this->e($message) . '</div>
    </div>
</body>
</html>';

        return Response::create($html, 'html', $status);
    }

    private function e(string $value): string
    {
        return htmlspecialchars($value, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
    }
}
