"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const core_1 = require("@oclif/core");
const chalk_1 = __importDefault(require("chalk"));
const ora_1 = __importDefault(require("ora"));
const cli_node_service_1 = require("../../lib/services/cli-node.service");
const resolve_cluster_1 = require("../../lib/resolve-cluster");
const POLL_INTERVAL_MS = 4000;
const MAX_WAIT_MS = 300_000; // 5 min
class NodeRemove extends core_1.Command {
    async run() {
        const { args, flags } = await this.parse(NodeRemove);
        const spinner = (0, ora_1.default)(`Queuing remove-worker for node ${args.nodeId}...`).start();
        let service;
        let clusterEntity;
        try {
            const resolved = await (0, resolve_cluster_1.resolveCluster)(flags.cluster);
            clusterEntity = resolved.entity;
            service = await cli_node_service_1.CliNodeService.create(resolved.id);
        }
        catch (error) {
            spinner.fail('Setup failed');
            console.log(chalk_1.default.red(`\n  Error: ${error.message}\n`));
            this.exit(1);
        }
        // Pre-check: resolve node and guard against master removal
        let nodes;
        try {
            nodes = await service.listNodes();
        }
        catch (error) {
            spinner.fail('Failed to fetch node list');
            console.log(chalk_1.default.red(`\n  Error: ${error.message}\n`));
            this.exit(1);
        }
        const target = nodes.find((n) => n.id === args.nodeId);
        if (!target) {
            spinner.fail('Node not found');
            console.log(chalk_1.default.red(`\n  Node "${args.nodeId}" not found in this cluster.\n`));
            console.log(chalk_1.default.dim('  Run `flui node list` to see available node IDs.\n'));
            this.exit(1);
        }
        if (target.nodeType === 'master') {
            this.refuseMasterRemoval(spinner, target, nodes, clusterEntity, flags.cluster);
        }
        let result;
        try {
            result = await service.removeWorker(args.nodeId);
        }
        catch (error) {
            spinner.fail('Failed to queue operation');
            const msg = error.response?.data?.message ?? error.message;
            console.log(chalk_1.default.red(`\n  Error: ${msg}\n`));
            this.exit(1);
        }
        spinner.succeed('Remove operation queued');
        console.log('');
        console.log(`  ${chalk_1.default.bold('Operation ID:')} ${result.operation_id}`);
        console.log(`  ${chalk_1.default.bold('Estimated:')}    ${result.estimated_duration}`);
        console.log('');
        if (flags['no-wait']) {
            console.log(chalk_1.default.dim('  Use `flui node list` to check when the node is gone.\n'));
            return;
        }
        await this.waitForRemoval(service, result.operation_id);
    }
    refuseMasterRemoval(spinner, target, nodes, clusterEntity, clusterFlag) {
        spinner.fail('Cannot remove the master node');
        const workers = nodes.filter((n) => n.nodeType === 'worker');
        const isObservability = clusterEntity.metadata?.isObservabilityCluster === true;
        console.log('');
        console.log(chalk_1.default.yellow(`  "${target.serverName}" is the master node of cluster "${clusterEntity.name}".`));
        if (workers.length > 0) {
            console.log(chalk_1.default.yellow(`  There ${workers.length === 1 ? 'is' : 'are'} still ${workers.length} worker node(s) running.`));
            console.log('');
            console.log(chalk_1.default.dim('  To scale down, remove the workers first:'));
            const clusterSuffix = clusterFlag ? ` --cluster ${clusterFlag}` : '';
            for (const w of workers) {
                console.log(chalk_1.default.dim(`    flui node remove ${w.id}${clusterSuffix}`));
            }
        }
        console.log('');
        console.log(chalk_1.default.dim('  To destroy the entire cluster (master + all workers):'));
        console.log(isObservability
            ? chalk_1.default.cyan('    flui env destroy')
            : chalk_1.default.cyan(`    flui cluster destroy ${clusterEntity.name}`));
        console.log('');
        this.exit(1);
    }
    async waitForRemoval(service, operationId) {
        console.log(chalk_1.default.dim(`  Draining and deleting node… (up to ${MAX_WAIT_MS / 60000} min)`));
        console.log('');
        const waitSpinner = (0, ora_1.default)('Draining node…').start();
        const started = Date.now();
        while (Date.now() - started < MAX_WAIT_MS) {
            await new Promise((r) => setTimeout(r, POLL_INTERVAL_MS));
            const done = await this.pollNodeRemoval(service, operationId, waitSpinner);
            if (done)
                return;
        }
        waitSpinner.warn('Timed out waiting for operation');
        console.log(chalk_1.default.yellow(`\n  Operation ${operationId} is still running.`));
        console.log(chalk_1.default.dim('  Run `flui node list` to check status when it completes.\n'));
    }
    async pollNodeRemoval(service, operationId, waitSpinner) {
        try {
            const op = await service.getOperationStatus(operationId);
            const pct = op.totalSteps > 0
                ? Math.round((op.currentStepIndex / op.totalSteps) * 100)
                : 0;
            waitSpinner.text = `Draining and deleting… ${pct}% (step ${op.currentStepIndex}/${op.totalSteps})`;
            if (op.status === 'COMPLETED') {
                const warned = op.metadata?.warnings?.some((w) => w.code === 'DRAIN_FAILED');
                if (warned) {
                    waitSpinner.warn(chalk_1.default.yellow('Node removed (drain failed — PDB may have blocked eviction)'));
                }
                else {
                    waitSpinner.succeed(chalk_1.default.green('Node removed successfully'));
                }
                console.log('');
                console.log(chalk_1.default.dim('  Run `flui node list` to see the updated node list.\n'));
                return true;
            }
            if (op.status === 'FAILED') {
                waitSpinner.fail('Operation failed');
                const errorMsg = op.metadata?.error ?? 'Unknown error';
                console.log(chalk_1.default.red(`\n  Error: ${errorMsg}\n`));
                this.exit(1);
            }
        }
        catch {
            /* polling error — keep trying */
        }
        return false;
    }
}
NodeRemove.description = 'Cordon, drain and remove a worker node from the cluster. Cannot remove the master.';
NodeRemove.examples = [
    '<%= config.bin %> <%= command.id %> <node-id>',
    '<%= config.bin %> <%= command.id %> <node-id> --no-wait',
];
NodeRemove.args = {
    nodeId: core_1.Args.string({
        description: 'Node ID to remove (from `flui node list`)',
        required: true,
    }),
};
NodeRemove.flags = {
    cluster: core_1.Flags.string({
        char: 'c',
        description: 'Cluster name or ID (default: auto-detect when only one cluster exists)',
    }),
    'no-wait': core_1.Flags.boolean({
        description: 'Return immediately after queuing the operation',
        default: false,
    }),
};
exports.default = NodeRemove;
