import { getBackblazePath } from "../-a-archives/archivesBackBlaze";
import { getGitURLLive, getGitRefLive } from "../4-deploy/git";
import { Querysub } from "../4-querysub/Querysub";
import { runPromise } from "../functional/runCommand";
import fs from "fs";
import os from "os";
import readline from "readline";
import open from "open";
import { fsExistsAsync } from "../fs";
// Import querysub, to fix missing dependencies
Querysub;

const pinnedNodeVersion = 22;

function getGitHubKeyCachePath(repoUrl: string): string {
    let repoOwner = "";
    let repoName = "";
    const sshMatch = repoUrl.match(/git@github\.com:([^/]+)\/(.+)\.git$/);
    const httpsMatch = repoUrl.match(/https:\/\/github\.com\/([^/]+)\/(.+)\.git$/);
    if (sshMatch) {
        repoOwner = sshMatch[1];
        repoName = sshMatch[2];
    } else if (httpsMatch) {
        repoOwner = httpsMatch[1];
        repoName = httpsMatch[2];
    }
    return os.homedir() + `/githubkey_${repoOwner}_${repoName}.json`;
}

async function verifyGitHubApiKey(apiKey: string): Promise<boolean> {
    const response = await fetch("https://api.github.com/user", {
        headers: {
            "Authorization": `Bearer ${apiKey}`,
            "Accept": "application/vnd.github+json"
        }
    });
    return response.ok;
}

async function getGitHubApiKey(repoUrl: string, sshRemote: string, forceRefresh = false): Promise<string> {
    // Parse repository info from URL
    let repoOwner = "";
    let repoName = "";
    // Handle both SSH and HTTPS formats
    // SSH: git@github.com:owner/repo.git
    // HTTPS: https://github.com/owner/repo.git
    const sshMatch = repoUrl.match(/git@github\.com:([^/]+)\/(.+)\.git$/);
    const httpsMatch = repoUrl.match(/https:\/\/github\.com\/([^/]+)\/(.+)\.git$/);

    if (sshMatch) {
        repoOwner = sshMatch[1];
        repoName = sshMatch[2];
    } else if (httpsMatch) {
        repoOwner = httpsMatch[1];
        repoName = httpsMatch[2];
    }

    const cacheFile = getGitHubKeyCachePath(repoUrl);

    // Check if we have a cached key
    if (!forceRefresh && await fsExistsAsync(cacheFile)) {
        try {
            const cached = JSON.parse(fs.readFileSync(cacheFile, "utf8"));
            if (cached.apiKey) {
                if (await verifyGitHubApiKey(cached.apiKey)) {
                    console.log(`✅ Using cached GitHub API key from ${cacheFile}`);
                    return cached.apiKey;
                }
                console.warn(`⚠️  Cached GitHub API key at ${cacheFile} failed verification (401 Bad credentials) — requesting a new one`);
            }
        } catch {
            // Invalid cache file, we'll ask for a new key
        }
    }

    // Need to get a new API key from user
    console.log("\n🔑 GitHub API key required for private repository access");
    console.log("Opening GitHub token creation page...");

    // Construct URL for classic token (fine-grained tokens don't support deploy keys)
    const repoInfo = repoOwner && repoName ? ` for repository ${repoOwner}/${repoName}` : "";
    const instructions = `yarn setup-machine ${sshRemote}${repoInfo}
1) Set expiration to 'No expiration' 
2) Check the 'repo' scope (full repository access)
3) Click 'Generate token'`;

    let tokenUrl = `https://github.com/settings/tokens/new?description=${encodeURIComponent(instructions)}&scopes=repo`;
    if (repoOwner && repoName) {
        console.log(`Setting up access for repository: ${repoOwner}/${repoName}`);
    }

    await open(tokenUrl);

    const rl = readline.createInterface({
        input: process.stdin,
        output: process.stdout
    });

    const apiKey = await new Promise<string>((resolve) => {
        const repoInfo = repoOwner && repoName ? ` for repository ${repoOwner}/${repoName}` : "";
        rl.question(`Please paste your GitHub API token (${repoInfo}): `, (answer) => {
            rl.close();
            resolve(answer.trim());
        });
    });

    // Cache the key
    fs.writeFileSync(cacheFile, JSON.stringify({ apiKey }));
    console.log(`✅ Caching GitHub API key to ${cacheFile}`);

    return apiKey;
}

async function addDeployKeyToGitHub(sshPublicKey: string, keyTitle: string, repoUrl: string, sshRemote: string): Promise<void> {
    // Parse repository info from URL to get owner/repo for the API endpoint
    let repoOwner = "";
    let repoName = "";
    const sshMatch = repoUrl.match(/git@github\.com:([^/]+)\/(.+)\.git$/);
    const httpsMatch = repoUrl.match(/https:\/\/github\.com\/([^/]+)\/(.+)\.git$/);

    if (sshMatch) {
        repoOwner = sshMatch[1];
        repoName = sshMatch[2];
    } else if (httpsMatch) {
        repoOwner = httpsMatch[1];
        repoName = httpsMatch[2];
    } else {
        throw new Error(`Could not parse GitHub repository from URL: ${repoUrl}`);
    }

    let url = `https://api.github.com/repos/${repoOwner}/${repoName}/keys`;
    console.log(url);

    let forceRefresh = false;
    while (true) {
        const apiKey = await getGitHubApiKey(repoUrl, sshRemote, forceRefresh);
        const response = await fetch(url, {
            method: "POST",
            headers: {
                "Authorization": `Bearer ${apiKey}`,
                "Accept": "application/vnd.github+json",
                "Content-Type": "application/json"
            },
            body: JSON.stringify({
                title: keyTitle,
                key: sshPublicKey,
                read_only: true
            })
        });

        if (response.ok) {
            console.log("✅ Deploy key added to GitHub repository");
            return;
        }

        const errorText = await response.text();
        if (response.status === 401 && !forceRefresh) {
            console.warn(`⚠️  GitHub API rejected credentials (401). Invalidating cached token and re-prompting.`);
            try {
                fs.unlinkSync(getGitHubKeyCachePath(repoUrl));
            } catch {
                // Cache file may not exist; ignore
            }
            forceRefresh = true;
            continue;
        }
        throw new Error(`Failed to add deploy key to GitHub repository: ${response.status} ${errorText}`);
    }
}

async function setupRepositoryOnRemote(sshRemote: string, gitURLLive: string, gitRefLive: string): Promise<void> {
    // Create git folder on remote
    await runPromise(`ssh ${sshRemote} "mkdir -p ~/machine-alwaysup"`);

    // Add bitbucket.org host key to remote machine's known_hosts
    await runPromise(`ssh ${sshRemote} "mkdir -p ~/.ssh"`);
    await runPromise(`ssh ${sshRemote} "ssh-keyscan -t rsa bitbucket.org >> ~/.ssh/known_hosts 2>/dev/null || true"`);
    await runPromise(`ssh ${sshRemote} "ssh-keyscan -t rsa github.com >> ~/.ssh/known_hosts 2>/dev/null || true"`);

    // Check if repo already exists, if not clone it
    try {
        await runPromise(`ssh ${sshRemote} "cd ~/machine-alwaysup && git status"`);
        console.log("Repository already exists, updating...");
        // Repository exists, update it
        await runPromise(`ssh ${sshRemote} "cd ~/machine-alwaysup && git remote update"`);
        await runPromise(`ssh ${sshRemote} "cd ~/machine-alwaysup && git add --all"`);
        await runPromise(`ssh ${sshRemote} "cd ~/machine-alwaysup && git stash"`);
        await runPromise(`ssh ${sshRemote} "cd ~/machine-alwaysup && git fetch --all"`);
        await runPromise(`ssh ${sshRemote} "cd ~/machine-alwaysup && git reset --hard ${gitRefLive}"`);
        await runPromise(`ssh ${sshRemote} "cd ~/machine-alwaysup && git prune"`);
    } catch {
        console.log("Cloning repository...");
        // Repository doesn't exist, clone it
        await runPromise(`ssh ${sshRemote} "git clone ${gitURLLive} ~/machine-alwaysup"`);
        await runPromise(`ssh ${sshRemote} "cd ~/machine-alwaysup && git reset --hard ${gitRefLive}"`);
    }
}

async function main() {
    let sshRemote = process.argv.slice(2).join(" ");
    if (!sshRemote) {
        console.error("Incorrect usage. Examples:\nyarn setup-machine 153.34.64.2\nyarn setup-machine devops@153.34.64.2");
        process.exit(1);
    }

    // Test command to verify ssh credentials work
    await runPromise(`ssh ${sshRemote} whoami`);

    // Detect Hetzner rescue system — if we're still in rescue, installimage hasn't been run yet
    let rescueProbe = await runPromise(`ssh ${sshRemote} "hostname; command -v installimage || true"`, { nothrow: true });
    if (/(^|\n)rescue(\s|$)/i.test(rescueProbe) || /installimage/.test(rescueProbe)) {
        console.error(`❌ Remote ${sshRemote} appears to be running the Hetzner rescue system (no OS installed yet).`);
        console.error(`   Run \`installimage\` on the remote first to provision the OS, reboot into the installed system, then re-run \`yarn setup-machine ${sshRemote}\`.`);
        console.error(`   Detected:\n${rescueProbe.trim()}`);
        process.exit(1);
    }

    // Setup swap space if not already configured
    console.log("Checking swap configuration...");
    const swapInfo = await runPromise(`ssh ${sshRemote} "free -m | grep Swap"`);
    const swapTotal = parseInt(swapInfo.split(/\s+/)[1]);

    // Get RAM amount to calculate appropriate swap size
    const memInfo = await runPromise(`ssh ${sshRemote} "free -m | grep Mem"`);
    const ramMB = parseInt(memInfo.split(/\s+/)[1]);
    if (Number.isNaN(swapTotal) || Number.isNaN(ramMB)) {
        console.error(`Error getting swap or memory info, swapInfo: ${swapInfo}, memInfo: ${memInfo}`);
    } else if (swapTotal > 0) {
        console.log(`✅ Swap already configured: ${swapTotal}MB SWAP vs ${ramMB}MB REAL MEMORY`);
    } else {
        // Calculate swap size based on RAM:
        // < 2GB RAM: 2x RAM
        // 2-8GB RAM: 1x RAM  
        // > 8GB RAM: 0.5x RAM (minimum 4GB)
        let swapSizeMB: number;
        if (ramMB < 2048) {
            swapSizeMB = ramMB * 2;
        } else if (ramMB <= 8192) {
            swapSizeMB = ramMB;
        } else {
            swapSizeMB = Math.max(Math.floor(ramMB * 0.5), 4096);
        }

        console.log(`Setting up swap space (${Math.round(swapSizeMB / 1024 * 10) / 10}GB for ${Math.round(ramMB / 1024 * 10) / 10}GB RAM)...`);

        // Create swap file with calculated size
        await runPromise(`ssh ${sshRemote} "sudo fallocate -l ${swapSizeMB}M /swapfile"`);

        // Set correct permissions
        await runPromise(`ssh ${sshRemote} "sudo chmod 600 /swapfile"`);

        // Set up the swap area
        await runPromise(`ssh ${sshRemote} "sudo mkswap /swapfile"`);

        // Enable the swap file
        await runPromise(`ssh ${sshRemote} "sudo swapon /swapfile"`);

        // Make swap persistent across reboots
        await runPromise(`ssh ${sshRemote} "echo '/swapfile none swap sw 0 0' | sudo tee -a /etc/fstab"`);

        // Verify swap is working
        const swapCheck = await runPromise(`ssh ${sshRemote} "free -m | grep Swap"`);
        console.log(`✅ Swap configured: ${swapCheck.split(/\s+/)[1]}MB total vs ${ramMB}MB REAL MEMORY`);
    }

    let backblazePath = getBackblazePath();

    console.log("Setting up machine:", sshRemote);

    // 0. Update apt
    console.log("Updating apt...");
    await runPromise(`ssh ${sshRemote} "sudo apt update"`);
    console.log("✅ Apt updated");

    // 1. Copy backblaze file to remote server (~/backblaze.json)
    console.log("Copying backblaze credentials...");
    if (await fsExistsAsync(backblazePath)) {
        await runPromise(`scp "${backblazePath}" ${sshRemote}:~/backblaze.json`);
        console.log("✅ Backblaze credentials copied");
    } else {
        console.warn("⚠️  Backblaze file not found at:", backblazePath);
    }

    // 2. Ensure git is installed on remote server
    console.log("Ensuring git is installed...");
    try {
        await runPromise(`ssh ${sshRemote} "which git"`);
        console.log("✅ Git already installed");
    } catch {
        console.log("Installing git...");
        await runPromise(`ssh ${sshRemote} "sudo apt update && sudo apt install -y git"`);
        console.log("✅ Git installed");
    }

    // 3. Ensure build tools are installed (needed for native modules)
    console.log("Ensuring build tools are installed...");
    try {
        await runPromise(`ssh ${sshRemote} "which make"`);
        console.log("✅ Build tools already installed");
    } catch {
        console.log("Installing build tools...");
        await runPromise(`ssh ${sshRemote} "sudo apt install -y build-essential"`);
        console.log("✅ Build tools installed");
    }

    // 3. Ensure nodejs is installed on remote server
    console.log("Ensuring Node.js is installed...");
    try {
        let nodeVersion = await runPromise(`ssh ${sshRemote} "node --version"`);
        console.log("✅ Node.js already installed:", nodeVersion.trim());
    } catch {
        console.log(`Installing Node.js (v${pinnedNodeVersion})...`);
        await runPromise(`ssh ${sshRemote} "curl -fsSL https://deb.nodesource.com/setup_${pinnedNodeVersion}.x | sudo -E bash -"`);
        await runPromise(`ssh ${sshRemote} "sudo apt-get install -y nodejs"`);
        let nodeVersion = await runPromise(`ssh ${sshRemote} "node --version"`);
        console.log("✅ Node.js installed:", nodeVersion.trim());
    }

    // 4. Ensure yarn is installed on remote server
    console.log("Ensuring yarn is installed...");
    try {
        await runPromise(`ssh ${sshRemote} "which yarn"`);
        console.log("✅ Yarn already installed");
    } catch {
        console.log("Installing yarn...");
        await runPromise(`ssh ${sshRemote} "npm install -g yarn"`);
        console.log("✅ Yarn installed");
    }

    console.log("Setting timezone to America/New_York...");
    await runPromise(`ssh ${sshRemote} "sudo timedatectl set-timezone America/New_York"`);
    console.log("✅ Timezone set to America/New_York");

    console.log(`Setting tmux scrollback history to 100000...`);
    await runPromise(`ssh ${sshRemote} "tmux set-option -g history-limit 100000"`);
    console.log("✅ Tmux scrollback history set to 100000");

    // 5. Clone current repo into ~/machine-alwaysup with SSH key handling for private repos
    console.log("Setting up repository...");
    let gitURLLive = await getGitURLLive();
    let gitRefLive = await getGitRefLive();

    try {
        await setupRepositoryOnRemote(sshRemote, gitURLLive, gitRefLive);
        console.log("✅ Repository cloned and set to correct reference");
    } catch (error) {
        // Check if this is a private repository access issue
        const errorMessage = error instanceof Error ? error.message : String(error);
        if (errorMessage.includes("Permission denied") || errorMessage.includes("Repository not found") || errorMessage.includes("fatal: Could not read from remote repository")) {
            console.log("⚠️  Repository appears to be private, setting up SSH key access...");

            // Ensure SSH key exists on remote machine
            await runPromise(`ssh ${sshRemote} "mkdir -p ~/.ssh"`);
            try {
                await runPromise(`ssh ${sshRemote} "test -f ~/.ssh/id_rsa"`);
                console.log("✅ SSH key already exists on remote machine");
            } catch {
                console.log("Generating SSH key on remote machine...");
                await runPromise(`ssh ${sshRemote} "ssh-keygen -t rsa -b 4096 -f ~/.ssh/id_rsa -N ''"`);
                console.log("✅ SSH key generated on remote machine");
            }

            // Get the public key from remote machine
            const sshPublicKey = await runPromise(`ssh ${sshRemote} "cat ~/.ssh/id_rsa.pub"`);
            const keyTitle = `Machine Setup - ${sshRemote} - ${new Date().toISOString()}`;

            // Add the deploy key to GitHub repository
            await addDeployKeyToGitHub(sshPublicKey.trim(), keyTitle, gitURLLive, sshRemote);

            // Retry repository setup
            console.log("Retrying repository setup with SSH key...");
            await setupRepositoryOnRemote(sshRemote, gitURLLive, gitRefLive);
            console.log("✅ Repository cloned and set to correct reference");
        } else {
            // Re-throw other errors
            throw error;
        }
    }

    // Install dependencies
    console.log("Installing dependencies...");
    await runPromise(`ssh ${sshRemote} "cd ~/machine-alwaysup && yarn install"`);
    console.log("✅ Dependencies installed");

    // 6. Create ~/machine-startup.sh which runs ~/machine-alwaysup/src/deployManager/machine.sh, and setup crontab
    console.log("Setting up cron job...");

    // Create startup script directly on remote machine
    await runPromise(`ssh ${sshRemote} "echo '#!/bin/bash' > ~/machine-startup.sh"`);
    await runPromise(`ssh ${sshRemote} "echo 'cd ~/machine-alwaysup/node_modules/querysub/src/deployManager' >> ~/machine-startup.sh"`);
    await runPromise(`ssh ${sshRemote} "echo 'bash machine.sh' >> ~/machine-startup.sh"`);
    await runPromise(`ssh ${sshRemote} "chmod +x ~/machine-startup.sh"`);

    // 7. Setup crontab to run ~/machine-startup.sh on startup
    const cronEntry = "@reboot ~/machine-startup.sh";

    // Get existing crontab, add our entry if not already present
    let existingCron = "";
    try {
        existingCron = await runPromise(`ssh ${sshRemote} "crontab -l"`, { nothrow: true });
    } catch {
        // No existing crontab is fine
    }

    if (!existingCron.includes("machine-startup.sh")) {
        // Add cron entry directly
        await runPromise(`ssh ${sshRemote} "(crontab -l 2>/dev/null || true; echo '${cronEntry}') | crontab -"`);
        console.log("✅ Cron job added");
    } else {
        console.log("✅ Cron job already exists");
    }

    // Start the machine service immediately
    console.log("Starting machine service...");
    await runPromise(`ssh ${sshRemote} "~/machine-startup.sh"`);
    console.log("✅ Machine service started!");

    console.log("\n🎉 Machine setup complete!");
}


main().catch(console.error).finally(() => process.exit(0));