"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 nest_app_1 = require("../../lib/nest-app");
const nip_base_domain_util_1 = require("../../lib/nip-base-domain.util");
const cli_observability_cluster_service_1 = require("../../services/cli-observability-cluster.service");
const firewall_provider_factory_1 = require("../../../../src/modules/providers/core/factories/firewall-provider.factory");
const ip_detection_1 = require("../../lib/utils/ip-detection");
const cli_firewall_repository_1 = require("../../lib/repositories/cli-firewall.repository");
const firewall_rules_1 = require("../../lib/templates/firewall-rules");
const context_banner_1 = require("../../lib/context-banner");
class EnvUpdateFirewall extends core_1.Command {
    async run() {
        const { flags } = await this.parse(EnvUpdateFirewall);
        (0, context_banner_1.printContextBanner)();
        let spinner = (0, ora_1.default)('Loading cluster information...').start();
        try {
            const app = await (0, nest_app_1.getNestApp)();
            const observabilityService = app.get(cli_observability_cluster_service_1.CliObservabilityClusterService);
            const ipService = app.get(ip_detection_1.IpDetectionService);
            const firewallFactory = app.get(firewall_provider_factory_1.FirewallProviderFactory);
            const firewallRepo = app.get(cli_firewall_repository_1.CliFirewallRepository);
            const cluster = await observabilityService.getObservabilityCluster();
            if (!cluster) {
                spinner.fail('No observability cluster found');
                console.log(chalk_1.default.yellow('\n⚠️  No observability cluster exists.\n'));
                console.log(chalk_1.default.dim('Create one with:'));
                console.log(`   ${chalk_1.default.cyan('flui env create')}\n`);
                return;
            }
            spinner.succeed(`Cluster found (${cluster.provider})`);
            const providerEnum = (cluster.provider || '').toLowerCase();
            const firewallService = firewallFactory.getFirewallProviderOrFail(providerEnum);
            const providerLabel = providerEnum.toUpperCase();
            let sourceCidrs;
            if (flags.ip) {
                sourceCidrs = ipService.parseCidrList(flags.ip);
                console.log(chalk_1.default.blue(`\nUsing custom IP(s): ${sourceCidrs.join(', ')}`));
            }
            else {
                spinner = (0, ora_1.default)('Detecting public IP...').start();
                const publicIp = await ipService.getPublicIp();
                sourceCidrs = [ipService.toCidr(publicIp)];
                spinner.succeed(`Auto-detected IP: ${sourceCidrs[0]}`);
            }
            spinner = (0, ora_1.default)('Finding firewall...').start();
            let existingFirewall = await firewallRepo.findByClusterId(cluster.id);
            if (!existingFirewall) {
                const byProvider = await firewallRepo.findByProvider(providerLabel);
                if (byProvider.length === 1) {
                    existingFirewall = byProvider[0];
                    spinner.text = `Adopting unlinked ${providerLabel} firewall ${existingFirewall.name}`;
                }
                else if (byProvider.length > 1) {
                    const masterIds = (cluster.nodes || [])
                        .filter((n) => n.nodeType === 'master')
                        .map((n) => {
                        const raw = String(n.providerResourceId || '');
                        const parts = raw.split(':');
                        return parts.at(-1);
                    })
                        .filter(Boolean);
                    spinner.text = `Disambiguating ${byProvider.length} firewall candidates by master attachment...`;
                    const matches = [];
                    for (const fw of byProvider) {
                        const details = await firewallService
                            .getFirewall(fw.id)
                            .catch(() => null);
                        if (!details)
                            continue;
                        const attached = new Set(details.appliedTo.map((a) => a.serverId));
                        if (masterIds.some((m) => attached.has(m))) {
                            matches.push(fw);
                        }
                    }
                    if (matches.length === 1) {
                        existingFirewall = matches[0];
                        spinner.text = `Found attached firewall ${existingFirewall.name}`;
                    }
                    else if (matches.length === 0) {
                        spinner.fail('No firewall currently attached to the cluster master');
                        this.exit(1);
                    }
                    else {
                        spinner.fail('Multiple firewalls attached, cannot disambiguate');
                        for (const f of matches)
                            this.log(`  - ${f.name} (${f.id})`);
                        this.exit(1);
                    }
                }
            }
            if (existingFirewall) {
                spinner.text = 'Updating firewall rules...';
                const newRules = (0, firewall_rules_1.OBSERVABILITY_FIREWALL_RULES)(sourceCidrs);
                await firewallService.updateFirewallRules(existingFirewall.id, newRules);
                existingFirewall.clusterId = cluster.id;
                existingFirewall.provider = providerLabel;
                existingFirewall.sourceCidrs = sourceCidrs;
                existingFirewall.rules = newRules;
                await firewallRepo.save(existingFirewall);
                spinner.succeed('Firewall updated successfully');
            }
            else {
                spinner.text = 'Creating firewall...';
                const firewallName = `flui-observability-${cluster.id}`;
                const rules = (0, firewall_rules_1.OBSERVABILITY_FIREWALL_RULES)(sourceCidrs);
                const result = await firewallService.createFirewall({
                    name: firewallName,
                    labels: [
                        { key: 'managed-by', value: 'flui-cloud' },
                        { key: 'flui-resource-type', value: 'firewall' },
                        { key: 'flui-cluster-id', value: cluster.id },
                        { key: 'flui-cluster-type', value: 'observability' },
                    ],
                    rules,
                    applyToLabelSelector: `flui-cluster-id=${cluster.id}`,
                });
                const serverIds = (cluster.nodes || [])
                    .map((n) => n.providerResourceId)
                    .filter((x) => typeof x === 'string' && x.length > 0);
                if (serverIds.length > 0) {
                    await firewallService.applyToServers(result.firewallId, serverIds);
                }
                await firewallRepo.save({
                    id: result.firewallId,
                    name: firewallName,
                    provider: providerLabel,
                    clusterId: cluster.id,
                    rules,
                    appliedToServerIds: serverIds,
                    sourceCidrs,
                    labels: [
                        { key: 'managed-by', value: 'flui-cloud' },
                        { key: 'flui-cluster-id', value: cluster.id },
                    ],
                });
                spinner.succeed('Firewall created successfully');
            }
            console.log(chalk_1.default.cyan('\n📋 Firewall Configuration:\n'));
            console.log(`   ${chalk_1.default.bold('Provider:')}   ${providerLabel}`);
            console.log(`   ${chalk_1.default.bold('Cluster:')}    ${cluster.name}`);
            console.log(`   ${chalk_1.default.bold('Source IP:')}  ${sourceCidrs.join(', ')}`);
            console.log('');
            console.log(chalk_1.default.bold('Exposed Services:'));
            console.log(`   SSH:         ${cluster.masterIpAddress}:22`);
            const baseDomain = (0, nip_base_domain_util_1.buildNipBaseDomain)(cluster.masterIpAddress, cluster.nipHostnameToken);
            console.log(`   Flui API:    https://api.${baseDomain}`);
            console.log(`   Dashboard:   https://app.${baseDomain}`);
            console.log(`   Grafana/Prometheus/Loki: cluster-internal (kubectl port-forward)`);
            console.log('');
        }
        catch (error) {
            spinner.fail('Failed to configure firewall');
            console.log(chalk_1.default.red('\n❌ Error:\n'));
            if (error instanceof Error) {
                console.log(`   ${error.message}\n`);
            }
            else {
                console.log(`   ${String(error)}\n`);
            }
            this.exit(1);
        }
        finally {
            await (0, nest_app_1.closeNestApp)();
        }
    }
}
EnvUpdateFirewall.description = 'Create or update firewall IP ranges for observability cluster';
EnvUpdateFirewall.examples = [
    '<%= config.bin %> <%= command.id %>',
    '<%= config.bin %> <%= command.id %> --ip 203.0.113.42',
    '<%= config.bin %> <%= command.id %> --ip "203.0.113.0/24,198.51.100.5/32"',
];
EnvUpdateFirewall.flags = {
    ip: core_1.Flags.string({
        description: 'Source IP/CIDR or comma-separated list (default: auto-detect current IP)',
        required: false,
    }),
};
exports.default = EnvUpdateFirewall;
