<?php
require_once __DIR__ . '/CloudflareConfig.php';

/**
 * Cloudflare API v4 客户端（Zone / DNS / SSL）
 */
class CloudflareClient
{
    private $token;
    private $accountId;

    public function __construct($token = null, $accountId = null)
    {
        $this->token     = $token ?? CloudflareConfig::apiToken();
        $this->accountId = $accountId ?? CloudflareConfig::accountId();
    }

    /**
     * @return array{ok:bool,result?:mixed,error?:string,code?:int,http_code?:int}
     */
    public function createZone($hostname)
    {
        $res = $this->request('POST', '/zones', [
            'name'    => $hostname,
            'account' => ['id' => $this->accountId],
            'type'    => 'full',
        ]);
        if (!$res['ok'] && (int) ($res['code'] ?? 0) === 1061) {
            return $this->findZoneByName($hostname);
        }
        return $res;
    }

    /**
     * @return array{ok:bool,result?:mixed,error?:string,code?:int,http_code?:int}
     */
    public function findZoneByName($hostname)
    {
        $path = '/zones?name=' . rawurlencode($hostname) . '&account.id=' . rawurlencode($this->accountId);
        $res  = $this->request('GET', $path);
        if (!$res['ok']) {
            return $res;
        }
        $zones = $res['result'] ?? [];
        if (!is_array($zones) || empty($zones[0])) {
            return ['ok' => false, 'error' => 'Zone 不存在'];
        }
        return ['ok' => true, 'result' => $zones[0]];
    }

    /**
     * @return array{ok:bool,result?:mixed,error?:string,code?:int,http_code?:int}
     */
    public function getZone($zoneId)
    {
        return $this->request('GET', '/zones/' . rawurlencode($zoneId));
    }

    /**
     * @return array{ok:bool,result?:mixed,error?:string,code?:int,http_code?:int}
     */
    public function deleteZone($zoneId)
    {
        return $this->request('DELETE', '/zones/' . rawurlencode($zoneId));
    }

    /**
     * @return array{ok:bool,result?:mixed,error?:string,code?:int,http_code?:int}
     */
    public function upsertARecord($zoneId, $fqdn, $ip, $proxied = true)
    {
        $path = '/zones/' . rawurlencode($zoneId) . '/dns_records?type=A&name=' . rawurlencode($fqdn);
        $list = $this->request('GET', $path);
        if (!$list['ok']) {
            return $list;
        }
        $records  = is_array($list['result'] ?? null) ? $list['result'] : [];
        $existing = $records[0] ?? null;
        $payload  = [
            'type'    => 'A',
            'name'    => $fqdn,
            'content' => $ip,
            'proxied' => (bool) $proxied,
            'ttl'     => 1,
        ];
        if (is_array($existing) && !empty($existing['id'])) {
            return $this->request('PATCH', '/zones/' . rawurlencode($zoneId) . '/dns_records/' . rawurlencode($existing['id']), $payload);
        }
        return $this->request('POST', '/zones/' . rawurlencode($zoneId) . '/dns_records', $payload);
    }

    /**
     * @return array{ok:bool,result?:mixed,error?:string,code?:int,http_code?:int}
     */
    public function setSslFlexible($zoneId)
    {
        return $this->request('PATCH', '/zones/' . rawurlencode($zoneId) . '/settings/ssl', ['value' => 'flexible']);
    }

    /**
     * @return array{ok:bool,result?:mixed,error?:string,code?:int,http_code?:int}
     */
    public function setAlwaysHttps($zoneId)
    {
        return $this->request('PATCH', '/zones/' . rawurlencode($zoneId) . '/settings/always_use_https', ['value' => 'on']);
    }

    /**
     * @return array{ok:bool,result?:mixed,error?:string,code?:int,http_code?:int}
     */
    private function request($method, $path, $body = null)
    {
        $url = 'https://api.cloudflare.com/client/v4' . $path;
        $ch  = curl_init($url);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
        curl_setopt($ch, CURLOPT_IPRESOLVE, CURL_IPRESOLVE_V4);
        curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, true);
        curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 2);
        curl_setopt($ch, CURLOPT_CUSTOMREQUEST, $method);
        $headers = [
            'Authorization: Bearer ' . $this->token,
            'Content-Type: application/json',
        ];
        curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
        if ($body !== null) {
            curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($body, JSON_UNESCAPED_UNICODE));
        }
        if (function_exists('cloak_curl_apply_upstream_defaults')) {
            cloak_curl_apply_upstream_defaults($ch, 5, 20);
        } else {
            curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 5);
            curl_setopt($ch, CURLOPT_TIMEOUT, 20);
        }

        $raw = curl_exec($ch);
        $httpCode = (int) curl_getinfo($ch, CURLINFO_HTTP_CODE);
        if ($raw === false) {
            $err = curl_error($ch);
            $errno = curl_errno($ch);
            curl_close($ch);
            self::logError($method, $path, 'curl', $errno . ':' . $err, $httpCode);
            return ['ok' => false, 'error' => 'API请求失败:' . $err . '(errno:' . $errno . ')', 'http_code' => $httpCode];
        }
        curl_close($ch);

        if ($raw === '') {
            self::logError($method, $path, 'empty_body', '', $httpCode);
            return ['ok' => false, 'error' => 'API响应为空(HTTP ' . $httpCode . ')', 'http_code' => $httpCode];
        }

        $decoded = json_decode($raw, true);
        if (!is_array($decoded)) {
            self::logError($method, $path, 'non_json', substr($raw, 0, 200), $httpCode);
            return ['ok' => false, 'error' => 'API响应非JSON(HTTP ' . $httpCode . ')', 'http_code' => $httpCode];
        }
        if (empty($decoded['success'])) {
            $msg  = $decoded['errors'][0]['message'] ?? '未知错误';
            $code = (int) ($decoded['errors'][0]['code'] ?? 0);
            self::logError($method, $path, 'api_error', $msg, $httpCode);
            return ['ok' => false, 'error' => $msg, 'code' => $code, 'http_code' => $httpCode];
        }
        return ['ok' => true, 'result' => $decoded['result'] ?? null, 'http_code' => $httpCode];
    }

    private static function logError($method, $path, $kind, $detail, $httpCode)
    {
        $line = date('Y-m-d H:i:s') . ' | Cloudflare ' . $method . ' ' . $path . ' | ' . $kind . ' | HTTP ' . $httpCode . ' | ' . $detail . "\n";
        @file_put_contents(dirname(__DIR__, 2) . '/err.txt', $line, FILE_APPEND);
    }
}
