<?php

declare(strict_types=1);

namespace app\common\library\scrm\spider;

use app\common\library\scrm\ScrmSpiderInterface;
use app\common\library\scrm\UnifiedScrmData;
use RuntimeException;

/**
 * 星河云控蜘蛛（纯 PHP cURL 抓取，不依赖 Node Puppeteer）
 *
 * 流程：访问带 token 的授权页建立 Cookie 会话 → 分页请求列表 API → 清洗为 UnifiedScrmData
 */
class XingheSpider implements ScrmSpiderInterface
{
    private const API_PATH = '/share/share/api_yinliu_count.html';

    private const DEFAULT_PER_PAGE_COUNT = 10;

    private const CURL_TIMEOUT_SECONDS = 15;

    /** @var resource|\CurlHandle|null */
    private $ch = null;

    /** @var string|null 临时 Cookie 文件路径，仅在 run() 成功后赋值 */
    private ?string $cookieFile = null;

    private string $pageUrl;

    private string $account;

    private string $password;

    private string $baseUrl;

    private UnifiedScrmData $unifiedData;

    /**
     * @param string $pageUrl  带 token 的授权页地址
     * @param string $account  账号（星河云控当前未使用，保留与工厂签名一致）
     * @param string $password 密码（星河云控当前未使用，保留与工厂签名一致）
     * @param string $nodeHost Node 地址（星河云控不使用，忽略）
     */
    public function __construct(
        string $pageUrl,
        string $account = '',
        string $password = '',
        string $nodeHost = ''
    ) {
        unset($nodeHost);

        $this->pageUrl = $pageUrl;
        $this->account = $account;
        $this->password = $password;
        $this->baseUrl = $this->resolveBaseUrl($pageUrl);
        $this->unifiedData = new UnifiedScrmData();
    }

    /**
     * 执行抓取并返回统一数据
     *
     * @throws RuntimeException
     */
    public function run(): UnifiedScrmData
    {
        $cookieFile = tempnam(sys_get_temp_dir(), 'xinghe_spider_cookie_');
        if ($cookieFile === false) {
            throw new RuntimeException('无法在系统临时目录创建 Cookie 文件');
        }

        $this->cookieFile = $cookieFile;
        $this->ch = curl_init();
        if ($this->ch === false) {
            throw new RuntimeException('cURL 初始化失败');
        }

        try {
            curl_setopt_array($this->ch, [
                CURLOPT_RETURNTRANSFER => true,
                CURLOPT_HEADER         => false,
                CURLOPT_TIMEOUT        => self::CURL_TIMEOUT_SECONDS,
                CURLOPT_USERAGENT      => 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Safari/537.36',
                CURLOPT_COOKIEJAR      => $this->cookieFile,
                CURLOPT_COOKIEFILE     => $this->cookieFile,
            ]);

            $this->authenticate();

            $firstPageData = $this->fetchApiData($this->buildApiUrl(1));
            $this->unifiedData->total = (int) ($firstPageData['count'] ?? 0);
            $this->unifiedData->todayNewCount = (int) ($firstPageData['totalRow']['day_sum'] ?? 0);

            $totalPages = (int) ceil($this->unifiedData->total / self::DEFAULT_PER_PAGE_COUNT);
            $allListPagesData = [$firstPageData['data'] ?? []];

            for ($page = 2; $page <= $totalPages; $page++) {
                $pageData = $this->fetchApiData($this->buildApiUrl($page));
                $allListPagesData[] = $pageData['data'] ?? [];
            }

            return $this->parseToUnifiedData($allListPagesData);
        } finally {
            $this->cleanup();
        }
    }

    /**
     * 访问授权页面，建立会话 Cookie
     */
    private function authenticate(): void
    {
        $this->sendRequest($this->pageUrl);
    }

    /**
     * 请求 API 并解析 JSON
     *
     * @return array<string, mixed>
     */
    private function fetchApiData(string $apiPath): array
    {
        $response = $this->sendRequest($this->baseUrl . $apiPath);
        $data = json_decode($response, true);

        if (json_last_error() !== JSON_ERROR_NONE) {
            throw new RuntimeException(
                'JSON 解析失败: ' . json_last_error_msg() . '。原始响应: ' . mb_substr($response, 0, 500, 'UTF-8')
            );
        }

        if (!is_array($data)) {
            throw new RuntimeException('API 返回数据格式无效');
        }

        return $data;
    }

    /**
     * @param list<mixed> $allListPagesData
     */
    private function parseToUnifiedData(array $allListPagesData): UnifiedScrmData
    {
        $unifiedData = $this->unifiedData;

        foreach ($allListPagesData as $records) {
            if (!is_array($records)) {
                continue;
            }
            foreach ($records as $item) {
                if (!is_array($item) || empty($item['user'])) {
                    continue;
                }
                $number = (string) $item['user'];
                $isOnline = isset($item['online']) && (int) $item['online'] === 1;
                $unifiedData->addNumber($number, $isOnline, (int) ($item['day_sum'] ?? 0));
            }
        }

        return $unifiedData;
    }

    /**
     * 发送 cURL 请求
     */
    private function sendRequest(string $url): string
    {
        if ($this->ch === null) {
            throw new RuntimeException('cURL 句柄未初始化');
        }

        curl_setopt($this->ch, CURLOPT_URL, $url);
        $response = curl_exec($this->ch);

        if ($response === false) {
            throw new RuntimeException(sprintf('请求失败 [%s]: %s', $url, curl_error($this->ch)));
        }

        $httpCode = (int) curl_getinfo($this->ch, CURLINFO_HTTP_CODE);
        if ($httpCode >= 400) {
            throw new RuntimeException(sprintf('HTTP 请求异常 [%s]，状态码: %d', $url, $httpCode));
        }

        return (string) $response;
    }

    /**
     * 构建分页 API 路径（相对 baseUrl）
     */
    private function buildApiUrl(int $page): string
    {
        return sprintf(
            '%s?page=%d&limit=%d&id=&class_id=&is_repet=1&start_time=&end_time=',
            self::API_PATH,
            $page,
            self::DEFAULT_PER_PAGE_COUNT
        );
    }

    /**
     * 从授权页 URL 解析站点根地址
     */
    private function resolveBaseUrl(string $pageUrl): string
    {
        $parsedUrl = parse_url($pageUrl);
        if ($parsedUrl === false || empty($parsedUrl['host'])) {
            throw new RuntimeException('工单链接格式无效，无法解析域名');
        }

        $scheme = isset($parsedUrl['scheme']) ? $parsedUrl['scheme'] . '://' : 'http://';
        $port   = isset($parsedUrl['port'])   ? $parsedUrl['port']   : '';
        if($port !== '' && $port !== 80 && $port !== 443) {
            $port = ':' . $port;
        }
        return $scheme . $parsedUrl['host'] . $port;
    }

    /**
     * 释放 cURL 资源并删除临时 Cookie 文件
     */
    private function cleanup(): void
    {
        if ($this->ch !== null) {
            if (is_resource($this->ch) || $this->ch instanceof \CurlHandle) {
                curl_close($this->ch);
            }
            $this->ch = null;
        }

        if ($this->cookieFile !== null && $this->cookieFile !== '' && is_file($this->cookieFile)) {
            @unlink($this->cookieFile);
        }
        $this->cookieFile = null;
    }

    /**
     * 兜底清理：防止 run() 异常退出时资源泄漏
     */
    public function __destruct()
    {
        $this->cleanup();
    }
}
