<?php
if (!defined('__KIMS__')) exit;

function jusqUpdateConfig()
{
    global $g, $d;
    include $g['path_module'].'admin/var/var.update.php';
    return $d['admin'];
}

function jusqUpdateFetch($url, $maxBytes)
{
    $config = jusqUpdateConfig();
    $parts = parse_url($url);
    if (!$parts || ($parts['scheme'] ?? '') !== 'https' || ($parts['host'] ?? '') !== $config['update_host']) {
        throw new Exception('허용되지 않은 업데이트 주소입니다.');
    }
    $ch = curl_init($url);
    curl_setopt_array($ch, array(
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_FOLLOWLOCATION => false,
        CURLOPT_CONNECTTIMEOUT => 10,
        CURLOPT_TIMEOUT => 30,
        CURLOPT_SSL_VERIFYPEER => true,
        CURLOPT_SSL_VERIFYHOST => 2,
        CURLOPT_PROTOCOLS => CURLPROTO_HTTPS,
        CURLOPT_USERAGENT => 'JusQ-Updater/1.0',
    ));
    $data = curl_exec($ch);
    $status = (int)curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
    $error = curl_error($ch);
    curl_close($ch);
    if ($data === false || $status !== 200) throw new Exception('업데이트 서버 연결 실패: '.($error ?: 'HTTP '.$status));
    if (strlen($data) > $maxBytes) throw new Exception('업데이트 응답 허용 크기를 초과했습니다.');
    return $data;
}

function jusqUpdateManifest()
{
    global $g;
    static $verifiedManifest = null;
    if ($verifiedManifest !== null) return $verifiedManifest;
    $config = jusqUpdateConfig();
    $cacheFile = $g['path_tmp'].'cache/jusq-update-manifest.json';
    $cached = is_file($cacheFile) ? json_decode(file_get_contents($cacheFile), true) : null;
    $json = '';
    $signatureText = '';
    if (is_array($cached) && !empty($cached['json']) && !empty($cached['signature']) && filemtime($cacheFile) > time()-600) {
        $json = base64_decode($cached['json'], true);
        $signatureText = $cached['signature'];
    } else {
        try {
            $json = jusqUpdateFetch($config['update_manifest_url'], 1048576);
            $signatureText = trim(jusqUpdateFetch($config['update_signature_url'], 4096));
        } catch (Throwable $e) {
            if (!is_array($cached) || empty($cached['json']) || empty($cached['signature']) || filemtime($cacheFile) <= time()-86400) throw $e;
            $json = base64_decode($cached['json'], true);
            $signatureText = $cached['signature'];
        }
    }
    $signature = base64_decode($signatureText, true);
    $publicKey = base64_decode($config['update_public_key'], true);
    if ($signature === false || $publicKey === false || !sodium_crypto_sign_verify_detached($signature, $json, $publicKey)) {
        throw new Exception('업데이트 목록의 전자서명이 올바르지 않습니다.');
    }
    $manifest = json_decode($json, true);
    if (!is_array($manifest) || ($manifest['schema'] ?? 0) !== 1 || ($manifest['product'] ?? '') !== 'jusq' || empty($manifest['latest']) || !isset($manifest['releases']) || !is_array($manifest['releases'])) {
        throw new Exception('업데이트 목록 형식이 올바르지 않습니다.');
    }
    if (!is_dir(dirname($cacheFile))) @mkdir(dirname($cacheFile), 0775, true);
    if (!is_file($cacheFile) || filemtime($cacheFile) <= time()-600) @file_put_contents($cacheFile, json_encode(array('json'=>base64_encode($json),'signature'=>$signatureText)), LOCK_EX);
    $verifiedManifest = $manifest;
    return $verifiedManifest;
}

function jusqUpdateDownload($url, $destination, $maxBytes)
{
    $config = jusqUpdateConfig();
    $parts = parse_url($url);
    if (!$parts || ($parts['scheme'] ?? '') !== 'https' || ($parts['host'] ?? '') !== $config['update_host']) throw new Exception('허용되지 않은 패키지 주소입니다.');
    $fp = fopen($destination, 'wb');
    if (!$fp) throw new Exception('업데이트 임시 파일을 만들 수 없습니다.');
    $ch = curl_init($url);
    curl_setopt_array($ch, array(
        CURLOPT_FILE => $fp,
        CURLOPT_FOLLOWLOCATION => false,
        CURLOPT_CONNECTTIMEOUT => 10,
        CURLOPT_TIMEOUT => 300,
        CURLOPT_SSL_VERIFYPEER => true,
        CURLOPT_SSL_VERIFYHOST => 2,
        CURLOPT_PROTOCOLS => CURLPROTO_HTTPS,
        CURLOPT_USERAGENT => 'JusQ-Updater/1.0',
        CURLOPT_NOPROGRESS => false,
        CURLOPT_XFERINFOFUNCTION => function($resource, $downloadSize, $downloaded) use ($maxBytes) { return $downloaded > $maxBytes ? 1 : 0; },
    ));
    $ok = curl_exec($ch);
    $status = (int)curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
    $error = curl_error($ch);
    curl_close($ch);
    fclose($fp);
    if (!$ok || $status !== 200 || !is_file($destination) || filesize($destination) > $maxBytes) {
        @unlink($destination);
        throw new Exception('업데이트 패키지 다운로드 실패: '.($error ?: 'HTTP '.$status));
    }
}

function jusqUpdateStatus($currentVersion)
{
    try {
        $manifest = jusqUpdateManifest();
        $available = false;
        foreach ($manifest['releases'] as $release) {
            if (empty($release['version']) || empty($release['build']) || empty($release['url']) || empty($release['sha256']) || empty($release['size'])) continue;
            if (version_compare($release['version'], $currentVersion, '<=')) continue;
            if (!empty($release['min_version']) && version_compare($currentVersion, $release['min_version'], '<')) continue;
            $available = true;
            break;
        }
        return array('ok'=>true, 'available'=>$available, 'manifest'=>$manifest, 'error'=>'');
    } catch (Throwable $e) {
        return array('ok'=>false, 'available'=>false, 'manifest'=>array('latest'=>$currentVersion, 'releases'=>array()), 'error'=>$e->getMessage());
    }
}

function jusqUpdateRelease($manifest, $build)
{
    foreach ($manifest['releases'] as $release) {
        if ((string)($release['build'] ?? '') === (string)$build) return $release;
    }
    throw new Exception('선택한 업데이트를 찾을 수 없습니다.');
}

function jusqUpdateSafePath($name)
{
    $name = str_replace('\\', '/', $name);
    if ($name === '' || strpos($name, "\0") !== false || $name[0] === '/' || preg_match('#(^|/)\.\.(/|$)#', $name) || preg_match('#^[A-Za-z]:/#', $name)) return false;
    return $name;
}

function jusqUpdateRemoveTree($path)
{
    if (!is_dir($path)) return;
    $items = scandir($path);
    foreach ($items as $item) {
        if ($item === '.' || $item === '..') continue;
        $target = $path.'/'.$item;
        if (is_dir($target) && !is_link($target)) jusqUpdateRemoveTree($target); else @unlink($target);
    }
    @rmdir($path);
}

function jusqUpdateEnsureDir($path)
{
    if (!is_dir($path) && !mkdir($path, 0775, true)) throw new Exception('디렉터리를 만들 수 없습니다: '.$path);
}
?>
