<?php
// ─── Domain Registrar Drivers ────────────────────────────────────────────────
// Every registrar is a row in h_registrar (name, driver, credentials, priority,
// status) — admin-managed, not hardcoded. `driver` selects which set of
// driver_{driver}_* functions handles that row's API calls:
//   - 'namesilo' / 'dynadot' — built-in drivers for those two APIs.
//   - 'generic'  — a config-only REST/JSON driver (URL templates + JSON path
//                  mappings stored in h_registrar.config) for wiring up most
//                  other simple JSON-REST registrar APIs WITHOUT touching
//                  this file. True zero-code support for every possible
//                  registrar API shape (OAuth, XML, HMAC-signed requests,
//                  SOAP, etc.) isn't realistic with one generic driver — for
//                  those, a new driver_<name>_* function set is the honest
//                  answer, same pattern as namesilo/dynadot below.
if (realpath(__FILE__) == realpath($_SERVER['SCRIPT_FILENAME'])) {
    header('HTTP/1.0 403 Forbidden', true, 403);
    exit;
}

function registrar_list_active() {
    global $conn;
    $rss = mysqli_query($conn, "SELECT * FROM h_registrar WHERE status='1' ORDER BY priority ASC, id ASC");
    $items = [];
    while ($row = mysqli_fetch_assoc($rss)) { $items[] = $row; }
    return $items;
}

function registrar_find($id) {
    global $conn;
    $id = (int)$id;
    return mysqli_fetch_assoc(mysqli_query($conn, "SELECT * FROM h_registrar WHERE id='$id'")) ?: null;
}

function registrar_tld_price_row($registrarId, $tld) {
    global $conn;
    $registrarId = (int)$registrarId;
    $tldSafe = mysqli_real_escape_string($conn, $tld);
    return mysqli_fetch_assoc(mysqli_query($conn, "SELECT * FROM h_registrar_tld WHERE registrar_id='$registrarId' AND tld='$tldSafe' AND status='1'")) ?: null;
}

/**
 * Applies the admin's per-TLD adjustment to a cost — either a fixed amount
 * (can be negative, e.g. -0.30 for a discount) or a percentage (can be
 * negative, e.g. -0.5 for 0.5% off). Applied to whatever the base cost is —
 * a live price from the registrar's own API if it returned one, otherwise
 * the admin's configured base_price.
 */
function apply_markup($cost, $type, $value) {
    $cost = (float)$cost;
    $value = (float)$value;
    $result = ($type === 'percent') ? $cost * (1 + $value / 100) : $cost + $value;
    return round(max(0, $result), 2);
}

function driver_dispatch($registrar, $action, $params) {
    $fn = 'driver_' . $registrar['driver'] . '_' . $action;
    if (!function_exists($fn)) return ['ok' => false, 'message' => "Driver '{$registrar['driver']}' does not support $action."];
    return $fn($registrar, ...$params);
}

/**
 * Requirement: "search all active registrars, check availability, compare
 * prices, automatically choose the cheapest, show the customer one price,
 * record internally which registrar will be used." This is that function.
 * The customer never sees or picks a registrar — only the result does.
 */
function shop_domain($domain) {
    $tld = strtolower(substr(strrchr($domain, '.'), 1));
    $best = null;
    $anyResponded = false;
    $anyAvailable = false; // true if ANY registrar confirms it's available, even if unpriced

    foreach (registrar_list_active() as $registrar) {
        $result = driver_dispatch($registrar, 'check_availability', [$domain]);
        if (!$result['ok']) continue;
        $anyResponded = true;
        if (empty($result['available'])) continue;
        $anyAvailable = true;

        // Cost is a live price from the driver itself if it returned one, otherwise
        // the admin's configured base_price for this TLD on this registrar. Either
        // way, the admin's markup/discount (fixed $ or %) is applied on top of it.
        $tldRow = registrar_tld_price_row($registrar['id'], $tld);
        $cost = $result['price'] ?? ($tldRow ? (float)$tldRow['base_price'] : null);
        if ($cost === null) continue; // this registrar can't price it — try the next one

        $markupType = $tldRow['markup_type'] ?? 'fixed';
        $markupValue = $tldRow ? (float)$tldRow['markup'] : 0;
        $price = apply_markup($cost, $markupType, $markupValue);

        if ($best === null || $price < $best['price']) {
            $best = ['registrar_id' => (int)$registrar['id'], 'registrar_name' => $registrar['name'], 'price' => (float)$price];
        }
    }

    if ($best !== null) return ['ok' => true] + $best + ['available' => true];
    if (!$anyResponded) return ['ok' => false, 'message' => 'No domain registrar is configured yet.'];
    if ($anyAvailable) {
        // Do NOT report this as "taken" — it's available, we just can't price it yet.
        return ['ok' => false, 'message' => 'This domain is available, but no active registrar has pricing configured for .' . $tld . ' — add it under Admin → Registrars → TLD Pricing.'];
    }
    return ['ok' => true, 'available' => false, 'message' => 'That domain is already taken.'];
}

function registrar_register_domain_via($registrarId, $domain, $years, $ns1, $ns2) {
    $registrar = registrar_find($registrarId);
    if (!$registrar || (int)$registrar['status'] !== 1) return ['ok' => false, 'message' => 'Registrar no longer available.'];
    return driver_dispatch($registrar, 'register', [$domain, $years, $ns1, $ns2]);
}

function registrar_change_nameservers_via($registrarId, $domain, $ns1, $ns2) {
    $registrar = registrar_find($registrarId);
    if (!$registrar) return ['ok' => false, 'message' => 'Registrar not found.'];
    return driver_dispatch($registrar, 'change_nameservers', [$domain, $ns1, $ns2]);
}

function registrar_get_domain_info_via($registrarId, $domain) {
    $registrar = registrar_find($registrarId);
    if (!$registrar) return ['ok' => false, 'message' => 'Registrar not found.'];
    return driver_dispatch($registrar, 'get_info', [$domain]);
}

// ─── NameSilo driver ─────────────────────────────────────────────────────────
function driver_namesilo_request($registrar, $operation, $params = []) {
    if (empty($registrar['api_key'])) return ['ok' => false, 'message' => 'NameSilo API key not set.'];
    $params = array_merge(['version' => '1', 'type' => 'json', 'key' => $registrar['api_key']], $params);
    $url = 'https://www.namesilo.com/api/' . $operation . '?' . http_build_query($params);

    $ch = curl_init();
    curl_setopt_array($ch, [CURLOPT_URL => $url, CURLOPT_RETURNTRANSFER => true, CURLOPT_TIMEOUT => 15]);
    $raw = curl_exec($ch);
    $err = curl_error($ch);
    curl_close($ch);
    if ($raw === false) return ['ok' => false, 'message' => "NameSilo request failed: $err"];

    $data = json_decode($raw, true);
    if (($data['reply']['code'] ?? null) != 300) {
        return ['ok' => false, 'message' => $data['reply']['detail'] ?? 'NameSilo request failed.'];
    }
    return ['ok' => true, 'data' => $data['reply']];
}

function driver_namesilo_check_availability($registrar, $domain) {
    $r = driver_namesilo_request($registrar, 'checkRegisterAvailability', ['domains' => $domain]);
    if (!$r['ok']) return $r;
    return ['ok' => true, 'available' => !empty($r['data']['available']['domain'] ?? null), 'price' => null];
}
function driver_namesilo_register($registrar, $domain, $years, $ns1, $ns2) {
    $r = driver_namesilo_request($registrar, 'registerDomain', ['domain' => $domain, 'years' => $years, 'private' => 1, 'auto_renew' => 0, 'ns1' => $ns1, 'ns2' => $ns2]);
    return ['ok' => $r['ok'], 'message' => $r['ok'] ? 'Registered via NameSilo.' : ($r['message'] ?? 'Registration failed.')];
}
function driver_namesilo_change_nameservers($registrar, $domain, $ns1, $ns2) {
    return driver_namesilo_request($registrar, 'changeNameServers', ['domain' => $domain, 'ns1' => $ns1, 'ns2' => $ns2]);
}
function driver_namesilo_get_info($registrar, $domain) {
    return driver_namesilo_request($registrar, 'getDomainInfo', ['domain' => $domain]);
}

// ─── Dynadot driver ──────────────────────────────────────────────────────────
// Verify parameter names against your Dynadot API version before relying on
// this in production — the plumbing is real, but Dynadot's API has changed
// shape across versions.
function driver_dynadot_request($registrar, $command, $params = []) {
    if (empty($registrar['api_key'])) return ['ok' => false, 'message' => 'Dynadot API key not set.'];
    $params = array_merge(['key' => $registrar['api_key'], 'command' => $command], $params);
    $url = 'https://api.dynadot.com/api3.json?' . http_build_query($params);

    $ch = curl_init();
    curl_setopt_array($ch, [CURLOPT_URL => $url, CURLOPT_RETURNTRANSFER => true, CURLOPT_TIMEOUT => 15]);
    $raw = curl_exec($ch);
    $err = curl_error($ch);
    curl_close($ch);
    if ($raw === false) return ['ok' => false, 'message' => "Dynadot request failed: $err"];

    $data = json_decode($raw, true);
    $key = $data ? array_key_first($data) : null;
    $body = $key ? ($data[$key] ?? []) : [];
    if (($body['ResponseCode'] ?? '1') !== '0') {
        return ['ok' => false, 'message' => $body['Error'] ?? 'Dynadot request failed.'];
    }
    return ['ok' => true, 'data' => $body];
}

function driver_dynadot_check_availability($registrar, $domain) {
    $r = driver_dynadot_request($registrar, 'search', ['domain0' => $domain]);
    if (!$r['ok']) return $r;
    $result = $r['data']['SearchResults'][0] ?? [];
    return [
        'ok' => true,
        'available' => strtolower($result['Available'] ?? 'no') === 'yes',
        'price' => isset($result['Price']) ? (float)$result['Price'] : null,
    ];
}
function driver_dynadot_register($registrar, $domain, $years, $ns1, $ns2) {
    $r = driver_dynadot_request($registrar, 'register', ['domain' => $domain, 'duration' => $years, 'ns0' => $ns1, 'ns1' => $ns2]);
    return ['ok' => $r['ok'], 'message' => $r['ok'] ? 'Registered via Dynadot.' : ($r['message'] ?? 'Registration failed.')];
}
function driver_dynadot_change_nameservers($registrar, $domain, $ns1, $ns2) {
    return driver_dynadot_request($registrar, 'set_ns', ['domain' => $domain, 'ns0' => $ns1, 'ns1' => $ns2]);
}
function driver_dynadot_get_info($registrar, $domain) {
    return driver_dynadot_request($registrar, 'domain_info', ['domain' => $domain]);
}

// ─── Generic REST driver — add new registrars via admin UI, no code ─────────
// h_registrar.config is JSON like:
// {
//   "check_url":    "https://api.example.com/check?domain={domain}&key={api_key}",
//   "available_path": "result.available",
//   "price_path":      "result.price",
//   "register_url": "https://api.example.com/register?domain={domain}&years={years}&ns1={ns1}&ns2={ns2}&key={api_key}",
//   "ns_url":       "https://api.example.com/setns?domain={domain}&ns1={ns1}&ns2={ns2}&key={api_key}",
//   "success_path": "result.success"
// }
// Works for any simple GET+JSON registrar API. Anything needing OAuth, XML,
// signed requests, etc. needs its own driver_<name>_* functions instead.
function driver_generic_fill($template, $registrar, $extra = []) {
    $vars = array_merge(['api_key' => $registrar['api_key'], 'api_secret' => $registrar['api_secret']], $extra);
    foreach ($vars as $k => $v) $template = str_replace('{' . $k . '}', rawurlencode((string)$v), $template);
    return $template;
}
function driver_generic_get_path($data, $path) {
    $val = $data;
    foreach (explode('.', $path) as $p) {
        if (!is_array($val) || !array_key_exists($p, $val)) return null;
        $val = $val[$p];
    }
    return $val;
}
function driver_generic_request($url) {
    $ch = curl_init();
    curl_setopt_array($ch, [CURLOPT_URL => $url, CURLOPT_RETURNTRANSFER => true, CURLOPT_TIMEOUT => 15]);
    $raw = curl_exec($ch);
    $err = curl_error($ch);
    curl_close($ch);
    if ($raw === false) return ['ok' => false, 'message' => "Request failed: $err"];
    $data = json_decode($raw, true);
    if ($data === null) return ['ok' => false, 'message' => 'Invalid (non-JSON) response from registrar.'];
    return ['ok' => true, 'data' => $data];
}
function driver_generic_config($registrar) {
    return json_decode($registrar['config'] ?? '{}', true) ?: [];
}
function driver_generic_check_availability($registrar, $domain) {
    $cfg = driver_generic_config($registrar);
    if (empty($cfg['check_url'])) return ['ok' => false, 'message' => 'Registrar not fully configured (missing check_url).'];
    $r = driver_generic_request(driver_generic_fill($cfg['check_url'], $registrar, ['domain' => $domain]));
    if (!$r['ok']) return $r;
    $price = !empty($cfg['price_path']) ? driver_generic_get_path($r['data'], $cfg['price_path']) : null;
    return [
        'ok' => true,
        'available' => (bool) driver_generic_get_path($r['data'], $cfg['available_path'] ?? 'available'),
        'price' => $price !== null ? (float)$price : null,
    ];
}
function driver_generic_register($registrar, $domain, $years, $ns1, $ns2) {
    $cfg = driver_generic_config($registrar);
    if (empty($cfg['register_url'])) return ['ok' => false, 'message' => 'Registrar not fully configured (missing register_url).'];
    $r = driver_generic_request(driver_generic_fill($cfg['register_url'], $registrar, ['domain' => $domain, 'years' => $years, 'ns1' => $ns1, 'ns2' => $ns2]));
    if (!$r['ok']) return $r;
    $success = driver_generic_get_path($r['data'], $cfg['success_path'] ?? 'success');
    return ['ok' => (bool)$success, 'message' => $success ? 'Registered.' : 'Registration failed.'];
}
function driver_generic_change_nameservers($registrar, $domain, $ns1, $ns2) {
    $cfg = driver_generic_config($registrar);
    if (empty($cfg['ns_url'])) return ['ok' => false, 'message' => 'Registrar not fully configured (missing ns_url).'];
    $r = driver_generic_request(driver_generic_fill($cfg['ns_url'], $registrar, ['domain' => $domain, 'ns1' => $ns1, 'ns2' => $ns2]));
    if (!$r['ok']) return $r;
    $success = driver_generic_get_path($r['data'], $cfg['success_path'] ?? 'success');
    return ['ok' => (bool)$success, 'message' => $success ? 'Nameservers updated.' : 'Update failed.'];
}
function driver_generic_get_info($registrar, $domain) {
    return ['ok' => false, 'message' => 'Domain info lookup is not implemented for the generic driver.'];
}
