"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    var desc = Object.getOwnPropertyDescriptor(m, k);
    if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
      desc = { enumerable: true, get: function() { return m[k]; } };
    }
    Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
    Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
    o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
    var ownKeys = function(o) {
        ownKeys = Object.getOwnPropertyNames || function (o) {
            var ar = [];
            for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
            return ar;
        };
        return ownKeys(o);
    };
    return function (mod) {
        if (mod && mod.__esModule) return mod;
        var result = {};
        if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
        __setModuleDefault(result, mod);
        return result;
    };
})();
Object.defineProperty(exports, "__esModule", { value: true });
exports.backupAuthCallbackRouter = void 0;
const express_1 = require("express");
const crypto = __importStar(require("crypto"));
const uuid_1 = require("uuid");
const config_1 = require("../backup/config");
const providers_1 = require("../backup/providers");
const engine_1 = require("../backup/engine");
const scheduler_1 = require("../backup/scheduler");
const authz_1 = require("../middleware/authz");
const router = (0, express_1.Router)();
// Backup manages global cloud credentials + cross-project file transfer — admin only.
// (OAuth callback is served by `backupAuthCallbackRouter`, which is mounted without
// auth since it's invoked by the external OAuth provider redirect.)
router.use(authz_1.requireAdmin);
/** Pending OAuth states: maps state token → provider ID. Expires after 10 minutes. */
const pendingOAuthStates = new Map();
// Periodically clean up expired OAuth states
setInterval(() => {
    const now = Date.now();
    for (const [key, val] of pendingOAuthStates) {
        if (val.expires < now)
            pendingOAuthStates.delete(key);
    }
}, 5 * 60 * 1000).unref();
exports.default = router;
// OAuth callback router — must be mounted WITHOUT auth middleware
exports.backupAuthCallbackRouter = (0, express_1.Router)();
// ── Provider CRUD ──────────────────────────────────────────────────────────────
// GET /api/backup/providers — list all providers (strip secrets/tokens)
router.get('/providers', (_req, res) => {
    const config = (0, config_1.getBackupConfig)();
    const providers = config.providers.map((p) => ({
        id: p.id,
        type: p.type,
        label: p.label,
        clientId: p.clientId,
        clientSecret: '***',
        authorized: !!p.tokens,
    }));
    res.json(providers);
});
// GET /api/backup/built-in-oauth — which provider types have built-in OAuth credentials
router.get('/built-in-oauth', (_req, res) => {
    const config = (0, config_1.getBackupConfig)();
    const available = [];
    for (const t of ['google-drive', 'onedrive', 'dropbox']) {
        if (config.builtInOAuth?.[t]?.clientId)
            available.push(t);
    }
    res.json({ available });
});
// PUT /api/backup/built-in-oauth/:type — set built-in OAuth credentials for a provider type
router.put('/built-in-oauth/:type', (req, res) => {
    const type = req.params.type;
    const validTypes = ['google-drive', 'onedrive', 'dropbox'];
    if (!validTypes.includes(type)) {
        res.status(400).json({ error: 'Invalid provider type' });
        return;
    }
    const { clientId, clientSecret } = req.body;
    if (!clientId?.trim() || !clientSecret?.trim()) {
        res.status(400).json({ error: 'clientId and clientSecret are required' });
        return;
    }
    (0, config_1.setBuiltInOAuth)(type, { clientId: clientId.trim(), clientSecret: clientSecret.trim() });
    res.json({ success: true });
});
// POST /api/backup/providers — add new provider
router.post('/providers', (req, res) => {
    let { type, label, clientId, clientSecret } = req.body;
    if (!type || !label) {
        res.status(400).json({ error: 'type and label are required' });
        return;
    }
    const validTypes = ['google-drive', 'onedrive', 'dropbox'];
    if (!validTypes.includes(type)) {
        res.status(400).json({ error: `Invalid provider type. Must be one of: ${validTypes.join(', ')}` });
        return;
    }
    // Use built-in OAuth credentials if not provided
    if (!clientId || !clientSecret) {
        const builtIn = (0, config_1.getBuiltInOAuth)(type);
        if (!builtIn) {
            res.status(400).json({ error: '该云盘类型没有内置 OAuth 凭据，请手动填写 Client ID 和 Client Secret' });
            return;
        }
        clientId = builtIn.clientId;
        clientSecret = builtIn.clientSecret;
    }
    const config = (0, config_1.getBackupConfig)();
    const newProvider = {
        id: (0, uuid_1.v4)(),
        type,
        label: label.trim(),
        clientId: clientId.trim(),
        clientSecret: clientSecret.trim(),
    };
    config.providers.push(newProvider);
    (0, config_1.saveBackupConfig)(config);
    res.status(201).json({
        id: newProvider.id,
        type: newProvider.type,
        label: newProvider.label,
        authorized: false,
    });
});
// DELETE /api/backup/providers/:id — remove provider
router.delete('/providers/:id', (req, res) => {
    const { id } = req.params;
    const config = (0, config_1.getBackupConfig)();
    const filtered = config.providers.filter((p) => p.id !== id);
    if (filtered.length === config.providers.length) {
        res.status(404).json({ error: 'Provider not found' });
        return;
    }
    config.providers = filtered;
    (0, config_1.saveBackupConfig)(config);
    res.json({ success: true });
});
// ── OAuth2 ────────────────────────────────────────────────────────────────────
// GET /api/backup/auth/:id/url — generate OAuth2 authorization URL
router.get('/auth/:id/url', (req, res) => {
    const { id } = req.params;
    const config = (0, config_1.getBackupConfig)();
    const providerConfig = config.providers.find((p) => p.id === id);
    if (!providerConfig) {
        res.status(404).json({ error: 'Provider not found' });
        return;
    }
    try {
        const port = req.socket.localPort || 3001;
        const redirectUri = `http://localhost:${port}/api/backup/auth/callback`;
        const provider = (0, providers_1.createProvider)(providerConfig);
        const stateToken = crypto.randomBytes(16).toString('hex');
        pendingOAuthStates.set(stateToken, { providerId: providerConfig.id, expires: Date.now() + 10 * 60 * 1000 });
        const url = provider.getAuthUrl(redirectUri, stateToken);
        res.json({ url, redirectUri });
    }
    catch (err) {
        res.status(500).json({ error: err.message });
    }
});
// GET /api/backup/auth/callback — OAuth2 redirect callback (also registered on backupAuthCallbackRouter)
async function handleAuthCallback(req, res) {
    const { code, state } = req.query;
    if (!code || !state) {
        res.redirect('/?backup_auth=error&reason=missing_params');
        return;
    }
    // Validate CSRF state token
    const pending = pendingOAuthStates.get(state);
    if (!pending || pending.expires < Date.now()) {
        pendingOAuthStates.delete(state);
        res.redirect('/?backup_auth=error&reason=invalid_state');
        return;
    }
    pendingOAuthStates.delete(state);
    const config = (0, config_1.getBackupConfig)();
    const providerConfig = config.providers.find((p) => p.id === pending.providerId);
    if (!providerConfig) {
        res.redirect('/?backup_auth=error&reason=provider_not_found');
        return;
    }
    try {
        const port = req.socket.localPort || 3001;
        const redirectUri = `http://localhost:${port}/api/backup/auth/callback`;
        const provider = (0, providers_1.createProvider)(providerConfig);
        const tokens = await provider.handleCallback(code, redirectUri);
        // Save tokens to provider config
        const idx = config.providers.findIndex((p) => p.id === pending.providerId);
        if (idx >= 0) {
            config.providers[idx] = { ...providerConfig, tokens };
            (0, config_1.saveBackupConfig)(config);
        }
        res.redirect('/?backup_auth=success');
    }
    catch (err) {
        console.error('[Backup] OAuth callback error:', err);
        res.redirect('/?backup_auth=error&reason=token_exchange_failed');
    }
}
// OAuth redirect target — served only by `backupAuthCallbackRouter`, which is
// mounted BEFORE the auth-protected `backupRouter` and outside of it, so the
// external provider can hit `/api/backup/auth/callback` without a token.
// (Previously also registered on `router` itself, but that path is
// auth-gated now and unreachable; dropped to avoid dead-code drift.)
exports.backupAuthCallbackRouter.get('/callback', handleAuthCallback);
// ── Backup operations ─────────────────────────────────────────────────────────
// POST /api/backup/run/:projectId — trigger manual backup
router.post('/run/:projectId', async (req, res) => {
    const { projectId } = req.params;
    try {
        const results = await (0, engine_1.runBackup)(projectId);
        res.json({ results });
    }
    catch (err) {
        res.status(500).json({ error: err.message });
    }
});
// GET /api/backup/schedule — get schedule config
router.get('/schedule', (_req, res) => {
    const config = (0, config_1.getBackupConfig)();
    res.json(config.schedule);
});
// PUT /api/backup/schedule — update schedule
router.put('/schedule', (req, res) => {
    const { enabled, intervalMinutes } = req.body;
    const config = (0, config_1.getBackupConfig)();
    if (typeof enabled === 'boolean')
        config.schedule.enabled = enabled;
    if (typeof intervalMinutes === 'number' && intervalMinutes > 0) {
        config.schedule.intervalMinutes = intervalMinutes;
    }
    (0, config_1.saveBackupConfig)(config);
    (0, scheduler_1.restartScheduler)();
    res.json(config.schedule);
});
// GET /api/backup/excludes — get exclude patterns
router.get('/excludes', (_req, res) => {
    const config = (0, config_1.getBackupConfig)();
    res.json({ patterns: config.excludePatterns });
});
// PUT /api/backup/excludes — update exclude patterns
router.put('/excludes', (req, res) => {
    const { patterns } = req.body;
    if (!Array.isArray(patterns)) {
        res.status(400).json({ error: 'patterns must be an array of strings' });
        return;
    }
    const config = (0, config_1.getBackupConfig)();
    config.excludePatterns = patterns.filter((p) => typeof p === 'string');
    (0, config_1.saveBackupConfig)(config);
    res.json({ patterns: config.excludePatterns });
});
// GET /api/backup/history — get backup history
router.get('/history', (_req, res) => {
    res.json((0, config_1.getBackupHistory)());
});
//# sourceMappingURL=backup.js.map