<?php

declare(strict_types=1);

namespace app\common\service;

use fast\Http;
use think\Exception;

/**
 * Cloudflare API 服务层
 */
class CloudflareService
{
    private const API_BASE = 'https://api.cloudflare.com/client/v4';

    private string $apiToken;
    private string $accountId;
    private string $serverIp;

    public function __construct()
    {
        $config = config('cloudflare');
        $this->apiToken = (string)($config['api_token'] ?? '');
        $this->accountId = (string)($config['account_id'] ?? '');
        $this->serverIp = trim((string)($config['server_ip'] ?? ''));
        if ($this->apiToken === '' || $this->accountId === '') {
            throw new Exception('Cloudflare API 凭证未配置，请在 .env 中设置 cloudflare.api_token 与 cloudflare.account_id');
        }
    }

    /**
     * 创建 Cloudflare Zone
     *
     * @param string $domain 根域名
     * @return array{zone_id: string, name_servers: array, status: string}
     * @throws Exception
     */
    public function createZone(string $domain): array
    {
        $body = [
            'name'    => $domain,
            'account' => ['id' => $this->accountId],
            'type'    => 'full',
        ];
        $response = $this->request('POST', '/zones', $body);
        $result = $response['result'] ?? [];
        return [
            'zone_id'      => (string)($result['id'] ?? ''),
            'name_servers' => (array)($result['name_servers'] ?? []),
            'status'       => (string)($result['status'] ?? 'pending'),
        ];
    }

    /**
     * @throws Exception
     */
    public function getZone(string $zoneId): array
    {
        $response = $this->request('GET', '/zones/' . $zoneId);
        return (array)($response['result'] ?? []);
    }

    /**
     * @throws Exception
     */
    public function deleteZone(string $zoneId): bool
    {
        $this->request('DELETE', '/zones/' . $zoneId);
        return true;
    }

    /**
     * @throws Exception
     */
    public function listDnsRecords(string $zoneId): array
    {
        $response = $this->request('GET', '/zones/' . $zoneId . '/dns_records', [], ['per_page' => 100]);
        return (array)($response['result'] ?? []);
    }

    /**
     * 聚合检测 Zone/NS/DNS 状态；NS 已验证且 Zone 已激活时，静默校验并修正 A 记录、Proxied、SSL、HTTPS
     *
     * @param array $row 域名记录
     * @return array{zone_status: string, ns_status: string, dns_status: string, check_result: string}
     */
    public function detectDomain(array $row): array
    {
        $domain = strtolower(trim((string)($row['domain'] ?? '')));
        $zoneId = (string)($row['zone_id'] ?? '');
        $expectedNs = $this->decodeNameservers($row['nameservers'] ?? '');

        $zoneStatus = 'failed';
        $nsStatus = 'pending';
        $dnsStatus = 'pending';
        $messages = [];

        try {
            $zone = $this->getZone($zoneId);
            $cfStatus = (string)($zone['status'] ?? '');
            if ($cfStatus === 'active') {
                $zoneStatus = 'active';
                $messages[] = 'Zone:已激活';
            } elseif (in_array($cfStatus, ['pending', 'initializing'], true)) {
                $zoneStatus = 'pending';
                $messages[] = 'Zone:待激活';
            } else {
                $zoneStatus = 'failed';
                $messages[] = 'Zone:异常(' . $cfStatus . ')';
            }
        } catch (\Throwable $e) {
            $zoneStatus = 'failed';
            $messages[] = 'Zone:检测失败(' . $e->getMessage() . ')';
        }

        $currentNs = $this->getDomainNameservers($domain);
        if ($currentNs === []) {
            $nsStatus = 'pending';
            $messages[] = 'NS:待验证(未查询到NS记录)';
        } elseif ($this->compareNameservers($currentNs, $expectedNs)) {
            $nsStatus = 'verified';
            $messages[] = 'NS:已验证';
        } else {
            $nsStatus = 'pending';
            $messages[] = 'NS:待验证(当前NS与Cloudflare不一致)';
        }

        if ($zoneStatus !== 'active') {
            $dnsStatus = 'pending';
            $messages[] = 'DNS:待创建(Zone未激活)';
        } elseif ($nsStatus !== 'verified') {
            $dnsStatus = 'pending';
            $messages[] = 'DNS:待创建(NS未验证)';
        } elseif ($this->serverIp === '') {
            $dnsStatus = 'failed';
            $messages[] = 'DNS:未配置server_ip';
        } elseif (!filter_var($this->serverIp, FILTER_VALIDATE_IP)) {
            $dnsStatus = 'failed';
            $messages[] = 'DNS:server_ip格式无效';
        } else {
            if ($this->hasRootCnameConflict($zoneId, $domain)) {
                $dnsStatus = 'failed';
                $messages[] = 'DNS:根域名存在CNAME记录，无法创建A记录';
            } else {
                try {
                    $this->reconcileCloudflareConfig($zoneId, $domain, $this->serverIp);
                    if ($this->verifyRootARecord($zoneId, $domain, $this->serverIp)) {
                        $dnsStatus = 'created';
                        $messages[] = 'DNS:已创建(A=' . $this->serverIp . ')';
                    } else {
                        $dnsStatus = 'pending';
                        $messages[] = 'DNS:待创建(A记录未指向' . $this->serverIp . ')';
                    }
                } catch (\Throwable $e) {
                    $dnsStatus = 'failed';
                    $messages[] = 'DNS:操作失败(' . $e->getMessage() . ')';
                }
            }
        }

        return [
            'zone_status'  => $zoneStatus,
            'ns_status'    => $nsStatus,
            'dns_status'   => $dnsStatus,
            'check_result' => implode('; ', $messages),
        ];
    }

    /**
     * @throws Exception
     */
    private function listRootARecords(string $zoneId, string $domain): array
    {
        $response = $this->request('GET', '/zones/' . $zoneId . '/dns_records', [], [
            'type' => 'A',
            'name' => $domain,
        ]);
        return (array)($response['result'] ?? []);
    }

    /**
     * @throws Exception
     */
    private function hasRootCnameConflict(string $zoneId, string $domain): bool
    {
        $response = $this->request('GET', '/zones/' . $zoneId . '/dns_records', [], [
            'type' => 'CNAME',
            'name' => $domain,
        ]);
        return count((array)($response['result'] ?? [])) > 0;
    }

    /**
     * NS 已验证且 Zone 已激活时：读取 Proxied / SSL / HTTPS / A 记录，与期望值不一致则修正
     *
     * @throws Exception
     */
    private function reconcileCloudflareConfig(string $zoneId, string $domain, string $ip): void
    {
        $this->ensureZoneEdgeSettings($zoneId);
        $this->upsertRootARecord($zoneId, $domain, $ip);
    }

    /**
     * 读取 Zone 单项设置值
     *
     * @throws Exception
     */
    private function getZoneSettingValue(string $zoneId, string $settingId): string
    {
        $response = $this->request('GET', '/zones/' . $zoneId . '/settings/' . $settingId);
        return strtolower(trim((string)($response['result']['value'] ?? '')));
    }

    /**
     * SSL/TLS=Flexible、Always Use HTTPS=开启；已与期望一致则跳过 PATCH
     *
     * @throws Exception
     */
    private function ensureZoneEdgeSettings(string $zoneId): void
    {
        if ($this->getZoneSettingValue($zoneId, 'ssl') !== 'flexible') {
            $this->request('PATCH', '/zones/' . $zoneId . '/settings/ssl', [
                'value' => 'flexible',
            ]);
        }
        if ($this->getZoneSettingValue($zoneId, 'always_use_https') !== 'on') {
            $this->request('PATCH', '/zones/' . $zoneId . '/settings/always_use_https', [
                'value' => 'on',
            ]);
        }
    }

    /**
     * 创建或更新根域名 A 记录（IP 与 Proxied 与期望不一致时修正）
     *
     * @throws Exception
     */
    private function upsertRootARecord(string $zoneId, string $domain, string $ip): void
    {
        $records = $this->listRootARecords($zoneId, $domain);
        if ($records === []) {
            $this->request('POST', '/zones/' . $zoneId . '/dns_records', [
                'type'    => 'A',
                'name'    => $domain,
                'content' => $ip,
                'ttl'     => 1,
                'proxied' => true,
            ]);
            return;
        }

        foreach ($records as $record) {
            $recordId = (string)($record['id'] ?? '');
            $content = (string)($record['content'] ?? '');
            $proxied = (bool)($record['proxied'] ?? false);
            if ($recordId === '') {
                continue;
            }
            if ($content === $ip && $proxied) {
                continue;
            }
            $this->request('PATCH', '/zones/' . $zoneId . '/dns_records/' . $recordId, [
                'content' => $ip,
                'ttl'     => 1,
                'proxied' => true,
            ]);
        }
    }

    /**
     * 校验根域名 A 记录：content 指向 server_ip 且已开启 Proxy
     *
     * @throws Exception
     */
    private function verifyRootARecord(string $zoneId, string $domain, string $ip): bool
    {
        $records = $this->listRootARecords($zoneId, $domain);
        foreach ($records as $record) {
            $content = (string)($record['content'] ?? '');
            $proxied = (bool)($record['proxied'] ?? false);
            if ($content === $ip && $proxied) {
                return true;
            }
        }
        return false;
    }

    /**
     * @throws Exception
     */
    private function request(string $method, string $path, array $body = [], array $query = []): array
    {
        $url = self::API_BASE . $path;
        if ($query !== []) {
            $url .= '?' . http_build_query($query);
        }

        $options = [
            CURLOPT_HTTPHEADER => [
                'Authorization: Bearer ' . $this->apiToken,
                'Content-Type: application/json',
            ],
        ];

        $payload = $body !== [] ? json_encode($body, JSON_UNESCAPED_UNICODE) : '';

        if (strtoupper($method) === 'GET') {
            $result = Http::sendRequest($url, [], 'GET', $options);
        } elseif (strtoupper($method) === 'DELETE') {
            $result = Http::sendRequest($url, $payload, 'DELETE', $options);
        } else {
            $result = Http::sendRequest($url, $payload, strtoupper($method), $options);
        }

        if (!$result['ret']) {
            throw new Exception('Cloudflare API 请求失败: ' . ($result['msg'] ?? '未知错误'));
        }

        $decoded = json_decode((string)$result['msg'], true);
        if (!is_array($decoded)) {
            throw new Exception('Cloudflare API 响应解析失败');
        }

        if (empty($decoded['success'])) {
            $errors = $decoded['errors'] ?? [];
            $message = 'Cloudflare API 错误';
            if (is_array($errors) && isset($errors[0]['message'])) {
                $message = (string)$errors[0]['message'];
            }
            throw new Exception($message);
        }

        return $decoded;
    }

    private function decodeNameservers($nameservers): array
    {
        if (is_array($nameservers)) {
            return $nameservers;
        }
        if (is_string($nameservers) && $nameservers !== '') {
            $decoded = json_decode($nameservers, true);
            return is_array($decoded) ? $decoded : [];
        }
        return [];
    }

    private function getDomainNameservers(string $domain): array
    {
        $records = @dns_get_record($domain, DNS_NS);
        if (!is_array($records)) {
            return [];
        }
        $list = [];
        foreach ($records as $record) {
            if (!empty($record['target'])) {
                $list[] = $this->normalizeNs((string)$record['target']);
            }
        }
        return array_values(array_unique($list));
    }

    private function compareNameservers(array $current, array $expected): bool
    {
        if ($expected === []) {
            return false;
        }
        $currentNorm = array_map([$this, 'normalizeNs'], $current);
        $expectedNorm = array_map([$this, 'normalizeNs'], $expected);
        sort($currentNorm);
        sort($expectedNorm);
        return $currentNorm === $expectedNorm;
    }

    private function normalizeNs(string $ns): string
    {
        return rtrim(strtolower(trim($ns)), '.');
    }
}
