/**
 * Login Command
 *
 * Uses OAuth 2.0 Device Authorization Grant (RFC 8628) for CLI authentication.
 * Works in headless environments (SSH, containers) and is more secure than URL-based tokens.
 *
 * After authentication, the CLI:
 * 1. Lists user's organizations
 * 2. Prompts to select one (or auto-selects if only one)
 * 3. Sets the active organization on the session
 *
 * The device flow creates a Better Auth session that appears in the
 * Manage Devices page where users can revoke it. The CLI exchanges the
 * raw device-flow session token for a bearer-safe signed token and stores
 * that signed bearer locally.
 */
import { exec } from 'child_process';
import pc from 'picocolors';
import ora from 'ora';
import prompts from 'prompts';
import { storeCredentials } from '../lib/auth.js';
const AUTH_URL = process.env.QUICKBACK_AUTH_URL || 'https://api.quickback.dev';
const CLIENT_ID = 'quickback-cli';
export async function login() {
    console.log(pc.bold('\n🔐 Quickback Login\n'));
    const spinner = ora('Requesting device authorization...').start();
    try {
        // Step 1: Request device code
        const deviceCode = await requestDeviceCode();
        spinner.stop();
        // Step 2: Display user code and open browser
        console.log(pc.cyan('━'.repeat(50)));
        console.log();
        console.log(`  ${pc.bold('Your code:')} ${pc.green(pc.bold(formatUserCode(deviceCode.user_code)))}`);
        console.log();
        console.log(`  Visit: ${pc.underline(deviceCode.verification_uri)}`);
        console.log();
        console.log(pc.cyan('━'.repeat(50)));
        console.log();
        // Try to open browser
        const verificationUrl = deviceCode.verification_uri_complete || deviceCode.verification_uri;
        openBrowser(verificationUrl);
        // Step 3: Poll for Better Auth device token
        const pollSpinner = ora('Waiting for authorization...').start();
        const deviceToken = await pollForToken(deviceCode.device_code, deviceCode.interval);
        // Step 4: Exchange the raw device token for a bearer-safe CLI token plus
        // user/session metadata.
        pollSpinner.text = 'Completing authentication...';
        const sessionInfo = await exchangeToken(deviceToken.access_token);
        pollSpinner.stop();
        const cliToken = sessionInfo.access_token;
        console.log(pc.green('\n✓ Login successful!'));
        console.log(pc.gray(`Welcome, ${sessionInfo.user?.name || sessionInfo.user?.email || 'user'}!`));
        // Step 5: List and select organization
        let activeOrganization;
        const orgSpinner = ora('Loading organizations...').start();
        try {
            const organizations = await listOrganizations(cliToken);
            orgSpinner.stop();
            if (organizations.length > 0) {
                if (organizations.length === 1) {
                    // Auto-select if only one organization
                    activeOrganization = organizations[0];
                    console.log(pc.gray(`\nUsing organization: ${pc.bold(activeOrganization.name)}`));
                }
                else {
                    // Prompt user to select
                    console.log();
                    const response = await prompts({
                        type: 'select',
                        name: 'organization',
                        message: 'Select an organization:',
                        choices: organizations.map(org => ({
                            title: org.name,
                            description: org.slug,
                            value: org,
                        })),
                    });
                    if (response.organization) {
                        activeOrganization = response.organization;
                    }
                }
                // Set active organization on the session
                if (activeOrganization) {
                    const setOrgSpinner = ora('Setting active organization...').start();
                    await setActiveOrganization(cliToken, activeOrganization.id);
                    setOrgSpinner.stop();
                    console.log(pc.green(`✓ Active organization: ${activeOrganization.name}`));
                }
            }
        }
        catch (orgError) {
            orgSpinner.stop();
            // Don't fail login if org listing fails - user can still use CLI
            console.log(pc.yellow('\nCould not load organizations. You can set one later with: quickback org use <slug>'));
        }
        // Step 6: Store credentials with organization info
        await storeCredentials({
            token: cliToken,
            user: sessionInfo.user,
            expiresAt: new Date(Date.now() + sessionInfo.expires_in * 1000).toISOString(),
            organization: activeOrganization ? {
                id: activeOrganization.id,
                name: activeOrganization.name,
                slug: activeOrganization.slug,
            } : undefined,
        });
        // Only show tier copy when the server actually told us the tier.
        // Until the auth server wires a real tier field on user records, this
        // stays undefined and we say nothing — beats lying in either direction.
        if (sessionInfo.user?.tier === 'pro') {
            console.log(pc.cyan('\nYou have access to Pro templates.'));
        }
        else if (sessionInfo.user?.tier === 'free') {
            console.log(pc.gray('\nUpgrade to Pro for premium templates: https://quickback.dev/pricing'));
        }
        console.log();
    }
    catch (error) {
        spinner.stop();
        if (error instanceof Error) {
            console.error(pc.red('\n✗ Login failed:'), error.message);
        }
        process.exit(1);
    }
}
/**
 * Request a device code from the auth server
 */
async function requestDeviceCode() {
    const response = await fetch(`${AUTH_URL}/auth/v1/device/code`, {
        method: 'POST',
        headers: {
            'Content-Type': 'application/json',
            'User-Agent': 'quickback-cli',
        },
        body: JSON.stringify({
            client_id: CLIENT_ID,
            scope: 'openid profile email',
        }),
    });
    if (!response.ok) {
        const error = await response.json().catch(() => ({}));
        throw new Error(error.error_description || 'Failed to request device code');
    }
    return response.json();
}
/**
 * Poll for access token
 */
async function pollForToken(deviceCode, interval) {
    let pollingInterval = interval;
    const maxAttempts = 120; // 10 minutes max at 5s intervals
    let attempts = 0;
    return new Promise((resolve, reject) => {
        const poll = async () => {
            attempts++;
            if (attempts > maxAttempts) {
                reject(new Error('Authorization timed out. Please try again.'));
                return;
            }
            try {
                const response = await fetch(`${AUTH_URL}/auth/v1/device/token`, {
                    method: 'POST',
                    headers: {
                        'Content-Type': 'application/json',
                        'User-Agent': 'quickback-cli',
                    },
                    body: JSON.stringify({
                        grant_type: 'urn:ietf:params:oauth:grant-type:device_code',
                        device_code: deviceCode,
                        client_id: CLIENT_ID,
                    }),
                });
                if (response.ok) {
                    const token = await response.json();
                    resolve(token);
                    return;
                }
                const error = await response.json();
                switch (error.error) {
                    case 'authorization_pending':
                        // Continue polling
                        break;
                    case 'slow_down':
                        // Increase polling interval
                        pollingInterval += 5;
                        break;
                    case 'access_denied':
                        reject(new Error('Authorization was denied'));
                        return;
                    case 'expired_token':
                        reject(new Error('Device code expired. Please try again.'));
                        return;
                    default:
                        reject(new Error(error.error_description || error.error || 'Unknown error'));
                        return;
                }
                // Schedule next poll
                setTimeout(poll, pollingInterval * 1000);
            }
            catch (err) {
                // Network error - retry
                setTimeout(poll, pollingInterval * 1000);
            }
        };
        // Start polling after initial interval
        setTimeout(poll, pollingInterval * 1000);
    });
}
/**
 * Exchange Better Auth access token for CLI session info
 *
 * The device authorization flow returns a raw Better Auth session token.
 * This endpoint upgrades it into a bearer-safe signed token and returns
 * user/session metadata alongside that new CLI credential.
 */
async function exchangeToken(accessToken) {
    const response = await fetch(`${AUTH_URL}/cli/exchange`, {
        method: 'POST',
        headers: {
            'Authorization': `Bearer ${accessToken}`,
            'Content-Type': 'application/json',
            'User-Agent': 'quickback-cli',
        },
    });
    if (!response.ok) {
        const error = await response.json().catch(() => ({}));
        throw new Error(error.error_description || 'Failed to exchange token');
    }
    return response.json();
}
/**
 * List user's organizations
 */
async function listOrganizations(sessionToken) {
    const response = await fetch(`${AUTH_URL}/auth/v1/organization/list`, {
        method: 'GET',
        headers: {
            'Authorization': `Bearer ${sessionToken}`,
            'Content-Type': 'application/json',
            'User-Agent': 'quickback-cli',
        },
    });
    if (!response.ok) {
        throw new Error('Failed to list organizations');
    }
    const data = await response.json();
    // Better Auth returns a plain array; handle both formats
    return Array.isArray(data) ? data : (data.organizations || []);
}
/**
 * Set the active organization for the session
 */
async function setActiveOrganization(sessionToken, organizationId) {
    const response = await fetch(`${AUTH_URL}/auth/v1/organization/set-active`, {
        method: 'POST',
        headers: {
            'Authorization': `Bearer ${sessionToken}`,
            'Content-Type': 'application/json',
            'User-Agent': 'quickback-cli',
        },
        body: JSON.stringify({ organizationId }),
    });
    if (!response.ok) {
        const error = await response.json().catch(() => ({}));
        throw new Error(error.message || 'Failed to set active organization');
    }
}
/**
 * Format user code with dash for readability (e.g., ABCD-1234)
 */
function formatUserCode(code) {
    if (code.length === 8) {
        return `${code.slice(0, 4)}-${code.slice(4)}`;
    }
    return code;
}
/**
 * Try to open browser to verification URL
 */
function openBrowser(url) {
    const platform = process.platform;
    const openCmd = platform === 'darwin' ? 'open' : platform === 'win32' ? 'start' : 'xdg-open';
    exec(`${openCmd} "${url}"`, (error) => {
        if (error) {
            console.log(pc.yellow('Could not open browser automatically.'));
            console.log(`Open this URL in your browser: ${pc.cyan(url)}\n`);
        }
        else {
            console.log(pc.gray('Opening browser...\n'));
        }
    });
}
//# sourceMappingURL=login.js.map