<?php
declare(strict_types=1);

/**
 * Yuser 客户区主题 — Web 安装向导
 * 与本文件同目录下须已有：
 *   clientarea_default_theme_editor/
 *   themes/clientarea/default/
 * 访问：https://域名/public/install.php（或合并到站点 public 根后的 /install.php）
 */

session_start();

const PKG_VERSION = '1.0.17';
const LOCK_FILE = '.install_lock';
const HUB_API_DEFAULT = 'https://www.finally.chat/theme_license_api/index.php';
const GEETEST_ID_DEFAULT = 'f178e6f69841ca812f755c3f3af49670';

const REQUIRED_DIRS = [
    'clientarea_default_theme_editor',
    'themes/clientarea/default',
];

/** install.php 所在目录 = 本包 public 目录 */
$publicDir = __DIR__;
$lockPath = $publicDir . '/' . LOCK_FILE;

/** @return array<string, mixed>|null */
function install_lock_read(string $path): ?array
{
    if (!is_file($path)) {
        return null;
    }
    $j = json_decode((string) file_get_contents($path), true);
    return is_array($j) ? $j : null;
}

function install_h(string $s): string
{
    return htmlspecialchars($s, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
}

/** WHMCS 站点 public 根（与 index.php 同级） */
function install_site_public_root(): string
{
    $doc = rtrim((string) ($_SERVER['DOCUMENT_ROOT'] ?? ''), '/\\');
    if ($doc !== '' && is_dir($doc)) {
        return $doc;
    }
    return __DIR__;
}

/**
 * 仅检测 public 目录内两个必备文件夹
 * @return list<string> 已存在的相对路径
 */
function install_detect_payload(string $publicDir): array
{
    $found = [];
    foreach (REQUIRED_DIRS as $rel) {
        $abs = $publicDir . '/' . str_replace('/', DIRECTORY_SEPARATOR, $rel);
        if (is_dir($abs)) {
            $found[] = $rel;
        }
    }
    return $found;
}

function install_payload_complete(string $publicDir): bool
{
    return count(install_detect_payload($publicDir)) === count(REQUIRED_DIRS);
}

function install_is_writable_dir(string $dir): bool
{
    if (is_dir($dir)) {
        return is_writable($dir);
    }
    $parent = dirname($dir);
    return is_dir($parent) && is_writable($parent);
}

/**
 * @return array{ok: bool, files: int, error?: string}
 */
function install_copy_tree(string $src, string $dest): array
{
    if (!is_dir($src)) {
        return ['ok' => false, 'files' => 0, 'error' => '源目录不存在：' . $src];
    }
    if (!is_dir($dest)) {
        if (!@mkdir($dest, 0755, true) && !is_dir($dest)) {
            return ['ok' => false, 'files' => 0, 'error' => '无法创建目录：' . $dest];
        }
    }
    $count = 0;
    $it = new RecursiveIteratorIterator(
        new RecursiveDirectoryIterator($src, RecursiveDirectoryIterator::SKIP_DOTS),
        RecursiveIteratorIterator::SELF_FIRST
    );
    foreach ($it as $item) {
        $sub = $it->getSubPathname();
        $target = $dest . DIRECTORY_SEPARATOR . $sub;
        if ($item->isDir()) {
            if (!is_dir($target) && !@mkdir($target, 0755, true) && !is_dir($target)) {
                return ['ok' => false, 'files' => $count, 'error' => '无法创建：' . $target];
            }
            continue;
        }
        $parent = dirname($target);
        if (!is_dir($parent) && !@mkdir($parent, 0755, true)) {
            return ['ok' => false, 'files' => $count, 'error' => '无法创建：' . $parent];
        }
        if (!@copy($item->getPathname(), $target)) {
            return ['ok' => false, 'files' => $count, 'error' => '复制失败：' . $sub];
        }
        $count++;
    }
    return ['ok' => true, 'files' => $count];
}

/**
 * @param array<string, string> $cfg
 */
function install_write_portal_config(string $portalDir, array $cfg): bool
{
    $lines = ["<?php", "return ["];
    foreach ($cfg as $k => $v) {
        $lines[] = '    ' . var_export($k, true) . ' => ' . var_export($v, true) . ',';
    }
    $lines[] = '];';
    $lines[] = '';
    return file_put_contents($portalDir . '/config.php', implode("\n", $lines)) !== false;
}

function install_ensure_dir(string $dir): bool
{
    if (is_dir($dir)) {
        return true;
    }
    return @mkdir($dir, 0755, true) || is_dir($dir);
}

function install_portal_url(string $deployPublicRoot): string
{
    $scheme = (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off') ? 'https' : 'http';
    $host = $_SERVER['HTTP_HOST'] ?? 'localhost';
    $doc = rtrim((string) ($_SERVER['DOCUMENT_ROOT'] ?? ''), '/\\');
    $prefix = '';
    if ($doc !== '' && strpos($deployPublicRoot, $doc) === 0) {
        $prefix = str_replace('\\', '/', substr($deployPublicRoot, strlen($doc)));
    }
    return $scheme . '://' . $host . rtrim($prefix, '/') . '/clientarea_default_theme_editor/';
}

function install_theme_dir(string $publicDir): string
{
    return $publicDir . '/themes/clientarea/default';
}

function install_load_license_verify(string $publicDir): bool
{
    $file = install_theme_dir($publicDir) . '/yuser/license_verify.php';
    if (!is_file($file)) {
        return false;
    }
    require_once $file;
    return true;
}

/**
 * @return array{authorized: bool, message: string, domain: string, status: string}|null
 */
function install_license_status(string $publicDir): ?array
{
    if (!install_load_license_verify($publicDir)) {
        return null;
    }
    return yuser_license_assert_authorized();
}

/**
 * @return array{ok: true}|array{ok: false, error: string, license: array<string, mixed>}
 */
function install_require_license(string $publicDir): array
{
    if (!install_load_license_verify($publicDir)) {
        return [
            'ok' => false,
            'error' => '缺少授权校验模块 yuser/license_verify.php',
            'license' => ['authorized' => false, 'message' => '授权模块缺失', 'domain' => '', 'status' => 'missing'],
        ];
    }
    $lic = yuser_license_assert_authorized();
    if (!empty($lic['authorized'])) {
        return ['ok' => true];
    }
    return [
        'ok' => false,
        'error' => (string) ($lic['message'] ?? '当前域名未授权，无法安装'),
        'license' => $lic,
    ];
}

/**
 * @param array<string, string> $input
 * @return array{ok: bool, logs: list<string>, portal_url?: string, error?: string}
 */
function install_run(string $sourcePublic, array $input): array
{
    @set_time_limit(600);
    @ini_set('memory_limit', '256M');

    $logs = [];

    $licGate = install_require_license($sourcePublic);
    if (empty($licGate['ok'])) {
        return [
            'ok' => false,
            'logs' => $logs,
            'error' => $licGate['error'] ?? '域名未授权',
            'license_blocked' => true,
            'license' => $licGate['license'] ?? [],
        ];
    }
    $logs[] = '域名授权校验通过：' . yuser_license_normalize_domain($_SERVER['HTTP_HOST'] ?? '');

    if (!install_payload_complete($sourcePublic)) {
        $missing = array_diff(REQUIRED_DIRS, install_detect_payload($sourcePublic));
        return [
            'ok' => false,
            'logs' => $logs,
            'error' => 'public 目录缺少：' . implode('、', $missing),
        ];
    }

    $deployRoot = install_site_public_root();
    $srcReal = realpath($sourcePublic) ?: $sourcePublic;
    $dstReal = realpath($deployRoot) ?: $deployRoot;
    $totalFiles = 0;

    if ($srcReal !== $dstReal) {
        if (!is_writable($deployRoot)) {
            return ['ok' => false, 'logs' => $logs, 'error' => '站点 public 根目录不可写：' . $deployRoot];
        }
        foreach (REQUIRED_DIRS as $rel) {
            $src = $sourcePublic . '/' . str_replace('/', DIRECTORY_SEPARATOR, $rel);
            $dest = $deployRoot . '/' . str_replace('/', DIRECTORY_SEPARATOR, $rel);
            $logs[] = '正在复制到站点 public：' . $rel . ' …';
            $cp = install_copy_tree($src, $dest);
            if (!$cp['ok']) {
                return ['ok' => false, 'logs' => $logs, 'error' => $cp['error'] ?? '复制失败'];
            }
            $totalFiles += $cp['files'];
            $logs[] = '完成 ' . $rel . '（' . $cp['files'] . ' 个文件）';
        }
    } else {
        $logs[] = '两个目录已在站点 public 根目录，跳过文件复制';
    }

    $portalDir = $deployRoot . '/clientarea_default_theme_editor';
    $cfg = [
        'hub_api_base' => HUB_API_DEFAULT,
        'geetest_captcha_id' => GEETEST_ID_DEFAULT,
        'update_manifest_url' => '',
        'update_download_key' => '',
    ];
    if (!install_write_portal_config($portalDir, $cfg)) {
        return ['ok' => false, 'logs' => $logs, 'error' => '无法写入 clientarea_default_theme_editor/config.php'];
    }
    $logs[] = '已生成门户 config.php';

    $siteRoot = dirname($deployRoot);
    $writable = [
        $siteRoot . '/data',
        $siteRoot . '/data/clientarea_default_theme_backups',
        $deployRoot . '/themes/clientarea/default/assets_custom/css',
    ];
    foreach ($writable as $d) {
        install_ensure_dir($d);
        if (install_is_writable_dir($d)) {
            $logs[] = '目录可写：' . $d;
        } else {
            $logs[] = '⚠ 请手动 chmod 可写：' . $d;
        }
    }

    $themeDir = $deployRoot . '/themes/clientarea/default';
    $bootstrap = $themeDir . '/yuser/bootstrap.php';
    if (is_file($bootstrap)) {
        if (!function_exists('openssl_decrypt')) {
            return [
                'ok' => false,
                'logs' => $logs,
                'error' => '服务器未启用 PHP OpenSSL 扩展，无法解密主题',
            ];
        }
        if (!is_writable($themeDir)) {
            return [
                'ok' => false,
                'logs' => $logs,
                'error' => '主题目录不可写：' . $themeDir,
            ];
        }
        require_once $bootstrap;
        // install_require_license() 已通过；避免 materialize 内重复请求 API
        $unlock = yuser_theme_install_unlock($themeDir);
        $logs[] = '主题解密：已展开 ' . (int) $unlock['files'] . ' 个文件'
            . ((int) $unlock['errors'] > 0 ? '（失败 ' . (int) $unlock['errors'] . ' 个）' : '');
        if (!empty($unlock['merged'])) {
            $logs[] = '已合并入口模板：' . implode('、', $unlock['merged']);
        }
        if ((int) $unlock['yenc_removed'] > 0) {
            $logs[] = '已清理旧版 .yenc 文件：' . (int) $unlock['yenc_removed'] . ' 个';
        }
        $failedList = is_array($unlock['failed'] ?? null) ? $unlock['failed'] : [];
        if ($failedList !== []) {
            $shown = array_slice($failedList, 0, 8);
            $logs[] = '解密失败文件：' . implode('、', $shown)
                . (count($failedList) > 8 ? ' …' : '');
            $critical = array_filter($failedList, static function (string $rel): bool {
                return !yuser_is_noncritical_decrypt_failure($rel);
            });
            if ($critical !== []) {
                $logs[] = '⚠ 可能影响功能的文件：' . implode('、', array_slice($critical, 0, 5));
            } else {
                $logs[] = '上述失败项多为次要页面/插件资源，一般不影响客户区主流程';
            }
        }
        if (!yuser_theme_is_ready($themeDir)) {
            return [
                'ok' => false,
                'logs' => $logs,
                'error' => '主题解密未完成。请确认 themes/clientarea/default 可写，或访问 /yuser_theme_unlock.php 重试',
            ];
        }
        if ((int) $unlock['errors'] > 0) {
            $logs[] = '核心模板已就绪；少量文件解密失败通常可忽略，站点可正常使用';
        }
    }

    $portalUrl = install_portal_url($deployRoot);
    $lock = [
        'installed_at' => date('c'),
        'public_dir' => $sourcePublic,
        'deploy_public_root' => $deployRoot,
        'files' => $totalFiles,
        'portal_url' => $portalUrl,
    ];
    file_put_contents($sourcePublic . '/' . LOCK_FILE, json_encode($lock, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT) . "\n");

    return ['ok' => true, 'logs' => $logs, 'portal_url' => $portalUrl];
}

$lock = install_lock_read($lockPath);
$step = $_POST['step'] ?? ($_GET['step'] ?? 'welcome');
$error = '';
$result = null;
$payloadDirs = install_detect_payload($publicDir);
$missingPayload = !install_payload_complete($publicDir);
$missingList = array_diff(REQUIRED_DIRS, $payloadDirs);
$licenseStatus = !$missingPayload ? install_license_status($publicDir) : null;
$licenseOk = is_array($licenseStatus) && !empty($licenseStatus['authorized']);
$licensePortalUrl = function_exists('yuser_license_local_portal_url')
    ? yuser_license_local_portal_url()
    : install_portal_url(install_site_public_root());

if ($step === 'run' && $_SERVER['REQUEST_METHOD'] === 'POST') {
    if ($missingPayload) {
        $error = 'public 目录缺少：' . implode('、', $missingList);
        $step = 'form';
    } elseif ($lock !== null && empty($_POST['force_reinstall'])) {
        $error = '已安装过。若需重装请勾选「强制重新安装」。';
        $step = 'form';
    } elseif (empty($_SESSION['install_csrf']) || !hash_equals((string) $_SESSION['install_csrf'], (string) ($_POST['csrf'] ?? ''))) {
        $error = '会话已过期，请刷新页面重试';
        $step = 'form';
    } elseif (!$licenseOk) {
        $error = (string) ($licenseStatus['message'] ?? '当前域名未授权，无法安装');
        $step = 'form';
    } else {
        $result = install_run($publicDir, []);
        $step = !empty($result['ok']) ? 'done' : 'form';
        if (empty($result['ok'])) {
            $error = $result['error'] ?? '安装失败';
        }
    }
}

if (empty($_SESSION['install_csrf'])) {
    $_SESSION['install_csrf'] = bin2hex(random_bytes(16));
}

header('Content-Type: text/html; charset=utf-8');
?>
<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>Yuser 主题安装向导</title>
  <style>
    *{box-sizing:border-box}body{margin:0;font-family:system-ui,-apple-system,sans-serif;background:linear-gradient(160deg,#e8f3ff 0%,#f7f8fa 50%);min-height:100vh;padding:24px;color:#1d2129}
    .wrap{max-width:640px;margin:0 auto}.card{background:#fff;border-radius:12px;padding:28px 32px;box-shadow:0 8px 32px rgba(0,0,0,.08)}
    h1{font-size:22px;margin:0 0 8px}h2{font-size:16px;margin:24px 0 12px;color:#4e5969}.muted{color:#86909c;font-size:14px;line-height:1.6}
    label{display:block;font-size:13px;font-weight:600;margin:14px 0 6px;color:#4e5969}
    input[type=text],input[type=url]{width:100%;padding:10px 12px;border:1px solid #e5e6eb;border-radius:6px;font-size:14px}
    input:focus{outline:none;border-color:#165dff;box-shadow:0 0 0 2px rgba(22,93,255,.15)}
    .btn{display:inline-block;margin-top:20px;padding:11px 24px;background:#165dff;color:#fff;border:none;border-radius:6px;font-size:15px;font-weight:600;cursor:pointer;text-decoration:none}
    .btn:hover{background:#4080ff}.btn-ghost{background:#f2f3f5;color:#1d2129;margin-left:8px}
    .err{background:#ffece8;color:#f53f3f;padding:12px;border-radius:6px;font-size:14px;margin:16px 0}
    .ok-box{background:#e8ffea;color:#00b42a;padding:12px;border-radius:6px;font-size:14px;margin:16px 0}
    ul.log{font-size:13px;color:#4e5969;line-height:1.8;max-height:240px;overflow:auto;background:#f7f8fa;border-radius:6px;padding:12px 12px 12px 28px}
    .chk{display:flex;align-items:center;gap:8px;margin-top:12px;font-size:13px;font-weight:400}.chk input{width:auto}
    code{background:#f2f3f5;padding:2px 6px;border-radius:4px;font-size:12px;word-break:break-all}
    .steps{display:flex;gap:8px;margin-bottom:20px;font-size:12px;color:#86909c}.steps span.active{color:#165dff;font-weight:600}
    .dir-ok{color:#00b42a}.dir-miss{color:#f53f3f}
    .lic-ok{background:#e8ffea;color:#00b42a;padding:12px;border-radius:6px;font-size:14px;margin:16px 0}
    .lic-bad{background:#ffece8;color:#f53f3f;padding:12px;border-radius:6px;font-size:14px;margin:16px 0}
    .btn:disabled{opacity:.45;cursor:not-allowed}
  </style>
</head>
<body>
<div class="wrap">
  <div class="card">
    <h1>Yuser 客户区主题</h1>
    <p class="muted">安装向导 v<?php echo install_h(PKG_VERSION); ?> — 检测并安装本 <code>public</code> 目录下的主题与门户。</p>

    <h2>public 目录检测</h2>
    <p class="muted">路径：<code><?php echo install_h($publicDir); ?></code></p>
    <ul class="muted" style="padding-left:20px">
      <?php foreach (REQUIRED_DIRS as $rel):
          $ok = in_array($rel, $payloadDirs, true);
      ?>
      <li class="<?php echo $ok ? 'dir-ok' : 'dir-miss'; ?>"><?php echo $ok ? '✓' : '✗'; ?> <code><?php echo install_h($rel); ?></code></li>
      <?php endforeach; ?>
    </ul>

    <?php if (!$missingPayload && is_array($licenseStatus)): ?>
    <h2>域名授权</h2>
    <?php if ($licenseOk): ?>
    <div class="lic-ok">✓ 已授权 — 域名 <code><?php echo install_h((string) $licenseStatus['domain']); ?></code>，可以安装。</div>
    <?php else: ?>
    <div class="lic-bad">✗ 未授权 — <?php echo install_h((string) $licenseStatus['message']); ?>
      <?php if ($licenseStatus['domain'] !== ''): ?>（<code><?php echo install_h((string) $licenseStatus['domain']); ?></code>）<?php endif; ?>
      <a href="<?php echo install_h($licensePortalUrl); ?>" target="_blank">主题管理后台</a>
    </div>
    <?php endif; ?>
    <?php endif; ?>

<?php if ($missingPayload && $step !== 'form'): ?>
    <div class="err">请将完整安装包中的 <code>public</code> 目录内容上传齐全后再访问本页（须包含上述两个文件夹）。</div>
<?php elseif ($lock !== null && $step === 'welcome'): ?>
    <div class="ok-box">已于 <?php echo install_h((string) ($lock['installed_at'] ?? '')); ?> 安装完成。</div>
    <p class="muted">门户：<a href="<?php echo install_h((string) ($lock['portal_url'] ?? '/clientarea_default_theme_editor/')); ?>" target="_blank"><?php echo install_h((string) ($lock['portal_url'] ?? '')); ?></a></p>
    <a class="btn" href="?step=form&amp;force=1">重新安装</a>
<?php elseif ($step === 'done' && is_array($result) && !empty($result['ok'])): ?>
    <div class="ok-box">安装成功</div>
    <p><strong>主题管理后台</strong><br><a href="<?php echo install_h((string) ($result['portal_url'] ?? '')); ?>" target="_blank"><?php echo install_h((string) ($result['portal_url'] ?? '')); ?></a></p>
    <p class="muted">请在 WHMCS 后台将客户区主题选为 <code>default</code>。</p>
    <?php if (!empty($result['logs'])): ?>
    <h2>安装日志</h2>
    <ul class="log"><?php foreach ($result['logs'] as $line): ?><li><?php echo install_h($line); ?></li><?php endforeach; ?></ul>
    <?php endif; ?>
    <p class="muted">建议删除 <code>install.php</code> 及本安装包目录。</p>
<?php elseif ($step === 'form' || $error !== ''): ?>
    <?php if ($error !== ''): ?><div class="err"><?php echo install_h($error); ?></div><?php endif; ?>
    <?php if (is_array($result) && !empty($result['logs'])): ?>
    <ul class="log"><?php foreach ($result['logs'] as $line): ?><li><?php echo install_h($line); ?></li><?php endforeach; ?></ul>
    <?php endif; ?>

    <form method="post" action="">
      <input type="hidden" name="step" value="run">
      <input type="hidden" name="csrf" value="<?php echo install_h((string) $_SESSION['install_csrf']); ?>">

      <?php if ($lock !== null || !empty($_GET['force'])): ?>
      <p class="muted">重新安装将覆盖站点 public 内已有的主题与门户文件。</p>
      <label class="chk"><input type="checkbox" name="force_reinstall" value="1"> 强制重新安装</label>
      <?php else: ?>
      <p class="muted">点击开始安装，将主题与门户部署到站点 public 目录。</p>
      <?php endif; ?>

      <button type="submit" class="btn" <?php echo ($missingPayload || !$licenseOk) ? 'disabled' : ''; ?>>开始安装</button>
      <a class="btn btn-ghost" href="?step=welcome">返回</a>
    </form>
<?php else: ?>
    <?php
    $phpOk = version_compare(PHP_VERSION, '7.4.0', '>=');
    $sitePublic = install_site_public_root();
    $sameRoot = (realpath($publicDir) ?: $publicDir) === (realpath($sitePublic) ?: $sitePublic);
    ?>
    <p class="muted">把本压缩包里的 <strong>public</strong> 文件夹上传到服务器后，访问其中的 <code>install.php</code>。若 public 不在站点根目录，安装时会自动复制到：<br><code><?php echo install_h($sitePublic); ?></code></p>
    <?php if (!$phpOk): ?><div class="err">需要 PHP 7.4+，当前 <?php echo install_h(PHP_VERSION); ?>。</div><?php endif; ?>
    <?php if (!$missingPayload && $phpOk): ?>
    <div class="ok-box">两个目录已就绪<?php echo $sameRoot ? '，位于站点 public 根' : '，安装时将复制到站点 public 根'; ?>。</div>
    <?php if (is_array($licenseStatus) && !$licenseOk): ?>
    <div class="lic-bad">当前域名未授权，无法安装。请先 <a href="<?php echo install_h($licensePortalUrl); ?>" target="_blank">主题管理后台</a> 申请授权。</div>
    <?php endif; ?>
    <form method="post" action="" style="display:inline">
      <input type="hidden" name="step" value="run">
      <input type="hidden" name="csrf" value="<?php echo install_h((string) $_SESSION['install_csrf']); ?>">
      <button type="submit" class="btn" <?php echo $licenseOk ? '' : 'disabled'; ?>>开始安装</button>
    </form>
    <?php endif; ?>
<?php endif; ?>
  </div>
</div>
</body>
</html>
