<?php
declare(strict_types=1);

header('Content-Type: application/json; charset=utf-8');
header('X-Content-Type-Options: nosniff');

const MIN_PHP_VERSION = '7.4.0';

$rootDir = dirname(__DIR__);
$dataDir = $rootDir . DIRECTORY_SEPARATOR . 'data';
$uploadDir = $rootDir . DIRECTORY_SEPARATOR . 'uploads';
$dbFile = $dataDir . DIRECTORY_SEPARATOR . '.clouddrive.sqlite';
$configFile = $rootDir . DIRECTORY_SEPARATOR . 'config.php';
$lockFile = __DIR__ . DIRECTORY_SEPARATOR . 'install.lock';

function jsonResponse(bool $success, string $message = '', array $data = []): void
{
    echo json_encode([
        'success' => $success,
        'message' => $message,
        'data' => $data,
    ], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
    exit;
}

function requestData(): array
{
    $contentType = $_SERVER['CONTENT_TYPE'] ?? '';
    if (stripos($contentType, 'application/json') !== false) {
        $raw = file_get_contents('php://input');
        $decoded = json_decode($raw ?: '{}', true);
        return is_array($decoded) ? $decoded : [];
    }

    return $_POST;
}

function formatBytes(int $bytes): string
{
    if ($bytes >= 1073741824) {
        return round($bytes / 1073741824, 2) . 'G';
    }
    if ($bytes >= 1048576) {
        return round($bytes / 1048576, 2) . 'M';
    }
    if ($bytes >= 1024) {
        return round($bytes / 1024, 2) . 'K';
    }

    return $bytes . 'B';
}

function iniBytes(string $value): int
{
    $value = trim($value);
    if ($value === '') {
        return 0;
    }

    $unit = strtolower($value[strlen($value) - 1]);
    $number = (float) $value;
    return match ($unit) {
        'g' => (int) ($number * 1024 * 1024 * 1024),
        'm' => (int) ($number * 1024 * 1024),
        'k' => (int) ($number * 1024),
        default => (int) $number,
    };
}

function ensureDirectory(string $path): bool
{
    if (is_dir($path)) {
        return is_writable($path);
    }

    return mkdir($path, 0755, true) && is_writable($path);
}

function writeAtomic(string $file, string $content): void
{
    $tmp = $file . '.tmp.' . bin2hex(random_bytes(4));
    if (file_put_contents($tmp, $content, LOCK_EX) === false) {
        throw new RuntimeException('写入临时文件失败：' . basename($file));
    }

    if (!rename($tmp, $file)) {
        @unlink($tmp);
        throw new RuntimeException('写入文件失败：' . basename($file));
    }
}

function writeDenyFile(string $dir): void
{
    $content = <<<HTACCESS
<IfModule mod_authz_core.c>
    Require all denied
</IfModule>
<IfModule !mod_authz_core.c>
    Deny from all
</IfModule>

HTACCESS;

    writeAtomic($dir . DIRECTORY_SEPARATOR . '.htaccess', $content);
    writeAtomic($dir . DIRECTORY_SEPARATOR . 'index.html', '');
}

function installChecks(string $dataDir, string $rootDir, string $uploadDir): array
{
    $phpOk = version_compare(PHP_VERSION, MIN_PHP_VERSION, '>=');
    $sqliteOk = extension_loaded('pdo_sqlite');
    $dirsOk = ensureDirectory($dataDir) && ensureDirectory($uploadDir) && is_writable($rootDir) && is_writable(__DIR__);
    $uploadMax = iniBytes((string) ini_get('upload_max_filesize'));
    $postMax = iniBytes((string) ini_get('post_max_size'));
    $uploadOk = $uploadMax > 0 && $postMax > 0;

    $checks = [
        'php' => [
            'passed' => $phpOk,
            'detail' => $phpOk ? 'PHP ' . PHP_VERSION : '需要 PHP ' . MIN_PHP_VERSION . ' 以上，当前 ' . PHP_VERSION,
        ],
        'sqlite' => [
            'passed' => $sqliteOk,
            'detail' => $sqliteOk ? 'PDO SQLite 已启用' : '请启用 pdo_sqlite 扩展',
        ],
        'writable' => [
            'passed' => $dirsOk,
            'detail' => $dirsOk ? '根目录、install、data、uploads 可写' : '请检查根目录、install、data、uploads 权限',
        ],
        'upload' => [
            'passed' => $uploadOk,
            'detail' => $uploadOk ? '上传 ' . formatBytes($uploadMax) . ' / POST ' . formatBytes($postMax) : '上传配置不可用',
        ],
    ];

    $passed = true;
    foreach ($checks as $check) {
        if (!$check['passed']) {
            $passed = false;
            break;
        }
    }

    return ['passed' => $passed, 'checks' => $checks];
}

function createSchema(PDO $pdo): void
{
    $pdo->exec('PRAGMA foreign_keys = ON');
    $pdo->exec('
        CREATE TABLE IF NOT EXISTS users (
            id INTEGER PRIMARY KEY AUTOINCREMENT,
            username TEXT NOT NULL UNIQUE,
            password_hash TEXT NOT NULL,
            role TEXT NOT NULL DEFAULT "user",
            status INTEGER NOT NULL DEFAULT 1,
            storage_quota INTEGER NOT NULL DEFAULT 10737418240,
            used_storage INTEGER NOT NULL DEFAULT 0,
            created_at TEXT NOT NULL,
            updated_at TEXT NOT NULL,
            last_login_at TEXT
        )
    ');
    $pdo->exec('
        CREATE TABLE IF NOT EXISTS files (
            id INTEGER PRIMARY KEY AUTOINCREMENT,
            user_id INTEGER NOT NULL,
            parent_id INTEGER,
            name TEXT NOT NULL,
            type TEXT NOT NULL DEFAULT "document",
            extension TEXT,
            mime_type TEXT,
            storage_path TEXT,
            size INTEGER NOT NULL DEFAULT 0,
            is_folder INTEGER NOT NULL DEFAULT 0,
            is_starred INTEGER NOT NULL DEFAULT 0,
            is_shared INTEGER NOT NULL DEFAULT 0,
            is_trashed INTEGER NOT NULL DEFAULT 0,
            created_at TEXT NOT NULL,
            updated_at TEXT NOT NULL,
            deleted_at TEXT,
            FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE,
            FOREIGN KEY (parent_id) REFERENCES files(id) ON DELETE CASCADE
        )
    ');
    $pdo->exec('
        CREATE TABLE IF NOT EXISTS shares (
            id INTEGER PRIMARY KEY AUTOINCREMENT,
            file_id INTEGER NOT NULL,
            token TEXT NOT NULL UNIQUE,
            password_hash TEXT,
            expires_at TEXT,
            created_at TEXT NOT NULL,
            FOREIGN KEY (file_id) REFERENCES files(id) ON DELETE CASCADE
        )
    ');
    $pdo->exec('
        CREATE TABLE IF NOT EXISTS settings (
            key TEXT PRIMARY KEY,
            value TEXT NOT NULL,
            updated_at TEXT NOT NULL
        )
    ');
    $pdo->exec('CREATE INDEX IF NOT EXISTS idx_files_user_parent ON files(user_id, parent_id)');
    $pdo->exec('CREATE INDEX IF NOT EXISTS idx_files_user_trash ON files(user_id, is_trashed)');
}

function saveSetting(PDO $pdo, string $key, string $value, string $now): void
{
    $stmt = $pdo->prepare('
        INSERT INTO settings(key, value, updated_at)
        VALUES(:key, :value, :updated_at)
        ON CONFLICT(key) DO UPDATE SET value = excluded.value, updated_at = excluded.updated_at
    ');
    $stmt->execute([
        ':key' => $key,
        ':value' => $value,
        ':updated_at' => $now,
    ]);
}

function appBaseUrl(): string
{
    $scheme = (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off') ? 'https' : 'http';
    $host = $_SERVER['HTTP_HOST'] ?? '';
    $scriptDir = rtrim(str_replace('\\', '/', dirname($_SERVER['SCRIPT_NAME'] ?? '/install/ajax.php')), '/');
    $baseDir = rtrim(dirname($scriptDir), '/');

    if ($host === '') {
        return '..';
    }

    return $scheme . '://' . $host . ($baseDir === '' || $baseDir === '.' ? '' : $baseDir);
}

try {
    $action = $_GET['action'] ?? $_POST['action'] ?? '';

    if ($action === 'check') {
        $result = installChecks($dataDir, $rootDir, $uploadDir);
        $result['installed'] = is_file($lockFile);
        jsonResponse(true, $result['installed'] ? '系统已安装' : '环境检测完成', $result);
    }

    if ($action !== 'install') {
        jsonResponse(false, '未知操作');
    }

    if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
        jsonResponse(false, '请使用 POST 请求');
    }

    if (is_file($lockFile)) {
        jsonResponse(false, '系统已安装，如需重新安装请先删除 install/install.lock');
    }

    $checkResult = installChecks($dataDir, $rootDir, $uploadDir);
    if (!$checkResult['passed']) {
        jsonResponse(false, '环境检测未通过', $checkResult);
    }

    $input = requestData();
    $username = trim((string) ($input['username'] ?? $input['user'] ?? ''));
    $password = (string) ($input['password'] ?? $input['pass'] ?? '');
    $passwordConfirm = (string) ($input['password_confirm'] ?? $input['pass2'] ?? '');

    if ($username === '' || !preg_match('/^[a-zA-Z0-9_.@-]{3,32}$/', $username)) {
        jsonResponse(false, '管理员账号需为 3-32 位字母、数字、下划线、点、@ 或短横线');
    }

    if (strlen($password) < 6 || strlen($password) > 128) {
        jsonResponse(false, '管理员密码需为 6-128 位');
    }

    if ($password !== $passwordConfirm) {
        jsonResponse(false, '两次密码不一致');
    }

    writeDenyFile($dataDir);

    $pdo = new PDO('sqlite:' . $dbFile);
    $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
    $pdo->setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE, PDO::FETCH_ASSOC);
    $pdo->exec('PRAGMA journal_mode = WAL');
    $pdo->exec('PRAGMA foreign_keys = ON');

    $now = date('c');
    $passwordHash = password_hash($password, PASSWORD_DEFAULT);

    $pdo->beginTransaction();
    createSchema($pdo);

    $existingStmt = $pdo->prepare('SELECT id FROM users WHERE username = :username LIMIT 1');
    $existingStmt->execute([':username' => $username]);
    $existingUser = $existingStmt->fetch();

    if ($existingUser) {
        $stmt = $pdo->prepare('
            UPDATE users
            SET password_hash = :password_hash,
                role = "admin",
                status = 1,
                updated_at = :updated_at
            WHERE id = :id
        ');
        $stmt->execute([
            ':password_hash' => $passwordHash,
            ':updated_at' => $now,
            ':id' => $existingUser['id'],
        ]);
        $adminId = (int) $existingUser['id'];
    } else {
        $stmt = $pdo->prepare('
            INSERT INTO users(username, password_hash, role, status, created_at, updated_at)
            VALUES(:username, :password_hash, "admin", 1, :created_at, :updated_at)
        ');
        $stmt->execute([
            ':username' => $username,
            ':password_hash' => $passwordHash,
            ':created_at' => $now,
            ':updated_at' => $now,
        ]);
        $adminId = (int) $pdo->lastInsertId();
    }

    saveSetting($pdo, 'site_name', '香橙个人网盘系统', $now);
    saveSetting($pdo, 'db_driver', 'sqlite', $now);
    saveSetting($pdo, 'installed_at', $now, $now);
    saveSetting($pdo, 'admin_user_id', (string) $adminId, $now);
    $pdo->commit();

    $config = <<<'PHP'
<?php
return [
    'installed' => true,
    'installed_at' => '%s',
    'database' => [
        'driver' => 'sqlite',
        'path' => __DIR__ . '/data/.clouddrive.sqlite',
    ],
    'upload_path' => __DIR__ . '/uploads',
];

PHP;
    writeAtomic($configFile, sprintf($config, addslashes($now)));
    writeAtomic($lockFile, "installed_at={$now}\ndriver=sqlite\n");

    jsonResponse(true, '安装完成', [
        'admin_id' => $adminId,
        'database' => 'data/.clouddrive.sqlite',
        'admin_url' => appBaseUrl() . '/admin/',
        'login_url' => appBaseUrl() . '/admin/login.php',
    ]);
} catch (Throwable $e) {
    if (isset($pdo) && $pdo instanceof PDO && $pdo->inTransaction()) {
        $pdo->rollBack();
    }

    jsonResponse(false, '安装失败：' . $e->getMessage());
}
