/**
 * studiograph redeploy — trigger a redeploy of an existing Railway service
 *
 * Reads Railway IDs from workspace config (.studiograph/workspace.json).
 * Falls back to user config (~/.studiograph/user.json) for backwards compatibility.
 * Also accepts a Railway dashboard URL to set up the link:
 *   studiograph redeploy https://railway.com/project/<id>/service/<id>?environmentId=<id>
 */
import { Command } from 'commander';
import { intro, outro, password, log, spinner, confirm } from '@clack/prompts';
import { existsSync, readFileSync, writeFileSync } from 'fs';
import { join } from 'path';
import { loadUserConfig, saveUserConfig } from '../../core/user-config.js';
import { fetchRailwayVariables, missingRequiredSecrets, provisionRequiredRailwaySecrets, } from '../railway-provisioning.js';
import { pinStartCommand, fetchLatestNextVersion, readOwnVersion } from '../railway-pin.js';
const RAILWAY_API = 'https://backboard.railway.app/graphql/v2';
async function railwayQuery(token, query, variables = {}) {
    const res = await fetch(RAILWAY_API, {
        method: 'POST',
        headers: {
            'Authorization': `Bearer ${token}`,
            'Content-Type': 'application/json',
        },
        body: JSON.stringify({ query, variables }),
    });
    if (!res.ok) {
        throw new Error(`Railway API error: ${res.status} ${res.statusText}`);
    }
    const json = await res.json();
    if (json.errors?.length) {
        throw new Error(`Railway API: ${json.errors.map((e) => e.message).join(', ')}`);
    }
    return json.data;
}
function loadWorkspaceConfig() {
    const cfgPath = join(process.cwd(), '.studiograph', 'workspace.json');
    if (!existsSync(cfgPath))
        return null;
    try {
        return JSON.parse(readFileSync(cfgPath, 'utf-8'));
    }
    catch {
        return null;
    }
}
function saveWorkspaceRailway(config) {
    const cfgPath = join(process.cwd(), '.studiograph', 'workspace.json');
    writeFileSync(cfgPath, JSON.stringify(config, null, 2), 'utf-8');
}
/**
 * Extract a Railway project ID from a URL or bare ID string.
 */
function parseProjectId(input) {
    const clean = input.split('?')[0];
    const urlMatch = clean.match(/\/project\/([a-f0-9-]+)/);
    if (urlMatch)
        return urlMatch[1];
    if (/^[a-f0-9-]{36}$/.test(clean))
        return clean;
    return null;
}
export const redeployCommand = new Command('redeploy')
    .description('Trigger a redeploy of an existing Railway service')
    .argument('[project]', 'Railway project ID or dashboard URL (saves for future use)')
    .action(async (project) => {
    intro('Redeploy to Railway');
    const userConfig = loadUserConfig();
    const wsConfig = loadWorkspaceConfig();
    // Resolve Railway IDs from workspace config only
    let projectId = wsConfig?.railway?.project_id;
    let serviceId = wsConfig?.railway?.service_id;
    let environmentId = wsConfig?.railway?.environment_id;
    // Link project if provided
    if (project) {
        const parsed = parseProjectId(project);
        if (!parsed) {
            outro('Invalid project. Pass a Railway project ID or dashboard URL.');
            process.exit(1);
            return;
        }
        projectId = parsed;
        serviceId = undefined;
        environmentId = undefined;
    }
    // Resolve token
    let token = process.env.RAILWAY_TOKEN || userConfig.railway_token;
    if (!token) {
        const input = await password({
            message: 'Railway API token (create at https://railway.com/account/tokens)',
            validate: (v) => v.trim() ? undefined : 'Required',
        });
        if (typeof input === 'symbol') {
            outro('Cancelled');
            process.exit(0);
        }
        token = input.trim();
        userConfig.railway_token = token;
        saveUserConfig(userConfig);
        log.info('Token saved');
    }
    if (!projectId) {
        outro('No Railway project linked to this workspace. Run: studiograph redeploy <railway-dashboard-url>');
        process.exit(1);
        return;
    }
    // Resolve service and environment from API if not saved
    if (!serviceId || !environmentId) {
        const s2 = spinner();
        s2.start('Resolving service...');
        const data = await railwayQuery(token, `
        query($id: String!) {
          project(id: $id) {
            services { edges { node { id name } } }
            environments { edges { node { id name } } }
          }
        }
      `, { id: projectId });
        if (!environmentId) {
            environmentId = data.project.environments.edges[0]?.node.id;
        }
        if (!serviceId) {
            const services = data.project.services.edges.map((e) => e.node);
            if (services.length === 0) {
                s2.stop('');
                outro('No services found in this project.');
                process.exit(1);
                return;
            }
            serviceId = services[0].id;
            s2.stop(`Service: ${services[0].name}`);
        }
        else {
            s2.stop('Resolved');
        }
    }
    if (!environmentId) {
        const data = await railwayQuery(token, `
        query($id: String!) {
          project(id: $id) {
            environments { edges { node { id name } } }
          }
        }
      `, { id: projectId });
        environmentId = data.project.environments.edges[0]?.node.id;
    }
    if (!environmentId) {
        outro('Could not determine Railway environment.');
        process.exit(1);
        return;
    }
    // Save Railway IDs to workspace config (per-workspace)
    if (wsConfig) {
        wsConfig.railway = { project_id: projectId, service_id: serviceId, environment_id: environmentId };
        saveWorkspaceRailway(wsConfig);
    }
    // Phase 2: validate STUDIOGRAPH_JWT_SECRET + STUDIOGRAPH_MASTER_KEY
    // exist on the target service before redeploying. Missing either var
    // would cause the next boot to fail (JWT) or refuse to start (master
    // key — production is env-only). Prompt before generating; refusing
    // = no deploy. Idempotent: vars already set → no-op.
    if (!serviceId || !environmentId) {
        outro('Could not determine Railway service.');
        process.exit(1);
        return;
    }
    const validationSpinner = spinner();
    validationSpinner.start('Checking required env vars...');
    const ctx = { projectId, serviceId, environmentId };
    const queryFn = (q, v) => railwayQuery(token, q, v);
    const existing = await fetchRailwayVariables(queryFn, ctx);
    const missing = missingRequiredSecrets(existing);
    if (missing.length > 0) {
        validationSpinner.stop(`Missing: ${missing.map((m) => m.name).join(', ')}`);
        log.warn('Required env vars are missing on this Railway service:\n' +
            missing.map((m) => `  - ${m.name}: ${m.description}`).join('\n') + '\n' +
            'Without these, the next deploy will fail to start (auth/JWT) or refuse ' +
            'to boot (master key, production is env-only).');
        const ok = await confirm({
            message: `Generate and set ${missing.length} missing var(s) now?`,
            initialValue: true,
        });
        if (typeof ok === 'symbol' || !ok) {
            outro('Cancelled — fix env vars in the Railway dashboard, then re-run.');
            process.exit(1);
            return;
        }
        const provSpinner = spinner();
        provSpinner.start('Provisioning...');
        const set = await provisionRequiredRailwaySecrets(queryFn, ctx);
        provSpinner.stop(`Provisioned: ${set.join(', ')}`);
    }
    else {
        validationSpinner.stop('All required env vars present');
    }
    // Update start command to the current CLI version. FATAL on failure
    // — swallowing here (as the pre-A7 version did) left Railway running
    // the old broken start command while the CLI reported success.
    // See cli-owner-audit §6.2 + codex review appendix.
    const pkgVersion = readOwnVersion();
    const s = spinner();
    s.start('Updating start command...');
    try {
        const startCommand = await pinStartCommand((q, v) => railwayQuery(token, q, v), { serviceId, environmentId }, pkgVersion);
        s.stop(`Start command: ${startCommand}`);
    }
    catch (err) {
        s.stop('Pin failed — aborting redeploy');
        log.error(`Could not update Railway's start command to studiograph@${pkgVersion}: ` +
            (err instanceof Error ? err.message : String(err)) +
            '. The next deploy would still run the old version, so we are not triggering it. ' +
            'Re-check the Railway token + service IDs and rerun.');
        process.exit(1);
        return;
    }
    // Informational @next-version-lag check. Non-blocking. The recommended
    // invocation is `npx -y studiograph@<exact-version> redeploy`, which
    // makes pkgVersion match whatever npx just resolved. If that lags
    // @next, the owner may be intentionally pinning a tested version — we
    // just surface the gap and continue. See cli-owner-audit §6.2.
    const latestNext = await fetchLatestNextVersion();
    if (latestNext && latestNext !== pkgVersion) {
        log.warn(`Heads up: @next is now ${latestNext}; you're pinning ${pkgVersion}.`);
    }
    // Force a fresh deployment by updating DEPLOY_TS env var.
    const s2 = spinner();
    s2.start('Triggering redeploy...');
    try {
        await railwayQuery(token, `
        mutation($input: VariableCollectionUpsertInput!) {
          variableCollectionUpsert(input: $input)
        }
      `, {
            input: {
                projectId,
                environmentId,
                serviceId,
                variables: { DEPLOY_TS: String(Date.now()) },
            },
        });
        s2.stop('Redeploy triggered');
    }
    catch (err) {
        s2.stop('Redeploy failed');
        log.error(err instanceof Error ? err.message : String(err));
        process.exit(1);
        return;
    }
    outro('Redeploying — check Railway dashboard for progress.');
});
//# sourceMappingURL=redeploy.js.map