<?php

namespace auctionforge\inc\controllers;

/**
 * Cloudflare edge-cache purge support.
 *
 * Purges pages from Cloudflare's edge cache by URL so cached auction / lot
 * pages refresh the instant their data changes (event-driven, not TTL-based).
 *
 * Fleet-wide config — ONE account-scoped token works for every site.
 * Put this in wp-config.php (template site + provisioning template):
 *
 *     define('AF_CLOUDFLARE_API_TOKEN', '...');
 *
 * Token scopes: Zone > Cache Purge (Purge) + Zone > Zone (Read), All zones.
 * Zone IDs are auto-resolved from each site's own domain and cached, so there
 * is nothing to configure per site.
 *
 * @package auctionforge\inc\controllers
 */
class CloudflareSupport
{
    const API_BASE       = 'https://api.cloudflare.com/client/v4';
    const ZONE_CACHE_KEY = 'af_cf_zone_'; // transient key prefix + md5(host)
    const ZONE_TTL       = DAY_IN_SECONDS;
    const MAX_URLS       = 30;            // CF purge-by-URL batch limit (Free/Pro)

    /**
     * API token, from constant (preferred) or option fallback.
     *
     * @return string
     */
    public static function getToken()
    {
        if (defined('AF_CLOUDFLARE_API_TOKEN') && AF_CLOUDFLARE_API_TOKEN) {
            return (string) AF_CLOUDFLARE_API_TOKEN;
        }
        $opt = get_option('af_cloudflare_api_token');
        return $opt ? (string) $opt : '';
    }

    /**
     * @return bool
     */
    public static function isConfigured()
    {
        return self::getToken() !== '';
    }

    /**
     * Resolve (and cache) the Cloudflare zone id for a host, e.g.
     * "bid.donleyauctions.com" -> zone "donleyauctions.com".
     *
     * @param  string $host
     * @return string  zone id, or '' if not found / not configured
     */
    public static function getZoneIdForHost($host)
    {
        $host = strtolower(trim((string) $host));
        if ($host === '') {
            return '';
        }

        $key    = self::ZONE_CACHE_KEY . md5($host);
        $cached = get_transient($key);
        if ($cached !== false) {
            return $cached === 'none' ? '' : $cached;
        }

        $zoneId = self::lookupZoneId($host);
        set_transient($key, $zoneId === '' ? 'none' : $zoneId, self::ZONE_TTL);

        return $zoneId;
    }

    /**
     * Query Cloudflare for the zone whose name is the closest parent of $host.
     * Walks bid.donleyauctions.com -> donleyauctions.com -> com until a zone
     * the token can see is found.
     *
     * @param  string $host
     * @return string
     */
    private static function lookupZoneId($host)
    {
        $token = self::getToken();
        if ($token === '') {
            return '';
        }

        $parts = explode('.', $host);
        while (count($parts) >= 2) {
            $candidate = implode('.', $parts);
            $resp = wp_remote_get(
                self::API_BASE . '/zones?status=active&name=' . rawurlencode($candidate),
                array(
                    'headers' => array(
                        'Authorization' => 'Bearer ' . $token,
                        'Content-Type'  => 'application/json',
                    ),
                    'timeout' => 8,
                )
            );

            if (!is_wp_error($resp) && (int) wp_remote_retrieve_response_code($resp) === 200) {
                $body = json_decode(wp_remote_retrieve_body($resp), true);
                if (!empty($body['success']) && !empty($body['result'][0]['id'])) {
                    return (string) $body['result'][0]['id'];
                }
            }

            array_shift($parts); // drop leftmost label, try the parent domain
        }

        return '';
    }

    /**
     * Purge a set of absolute URLs from Cloudflare's edge cache.
     * Never throws — logs and returns false on failure so it can't break a
     * page render or a sync run.
     *
     * @param  string[] $urls
     * @return bool  true if at least one purge request succeeded (or nothing to do)
     */
    public static function purgeUrls($urls)
    {
        if (!self::isConfigured()) {
            return false;
        }

        $urls = array_values(array_unique(array_filter(array_map('strval', (array) $urls))));
        if (empty($urls)) {
            return true;
        }

        // Group URLs by their Cloudflare zone.
        $byZone = array();
        foreach ($urls as $url) {
            $host = wp_parse_url($url, PHP_URL_HOST);
            if (!$host) {
                continue;
            }
            $zoneId = self::getZoneIdForHost($host);
            if ($zoneId === '') {
                continue;
            }
            $byZone[$zoneId][] = $url;
        }

        if (empty($byZone)) {
            return false;
        }

        $token = self::getToken();
        $ok    = false;

        foreach ($byZone as $zoneId => $zoneUrls) {
            foreach (array_chunk(array_values(array_unique($zoneUrls)), self::MAX_URLS) as $chunk) {
                $resp = wp_remote_post(
                    self::API_BASE . '/zones/' . $zoneId . '/purge_cache',
                    array(
                        'headers' => array(
                            'Authorization' => 'Bearer ' . $token,
                            'Content-Type'  => 'application/json',
                        ),
                        'body'    => wp_json_encode(array('files' => array_values($chunk))),
                        'timeout' => 10,
                    )
                );

                if (!is_wp_error($resp) && (int) wp_remote_retrieve_response_code($resp) === 200) {
                    $ok = true;
                } else {
                    $msg = is_wp_error($resp)
                        ? $resp->get_error_message()
                        : wp_remote_retrieve_body($resp);
                    error_log('[AuctionForge][Cloudflare purge] failed (zone ' . $zoneId . '): ' . substr((string) $msg, 0, 300));
                }
            }
        }

        return $ok;
    }
}
