"use strict";
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
    var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
    if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
    else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
    return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var __metadata = (this && this.__metadata) || function (k, v) {
    if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
};
var __param = (this && this.__param) || function (paramIndex, decorator) {
    return function (target, key) { decorator(target, key, paramIndex); }
};
var ClusterCreationService_1;
Object.defineProperty(exports, "__esModule", { value: true });
exports.ClusterCreationService = void 0;
const common_1 = require("@nestjs/common");
const typeorm_1 = require("@nestjs/typeorm");
const typeorm_2 = require("typeorm");
const bull_1 = require("@nestjs/bull");
const cluster_entity_1 = require("../entities/cluster.entity");
const infrastructure_operations_entity_1 = require("../../servers/entities/infrastructure-operations.entity");
const encryption_service_1 = require("../../../shared/encryption/services/encryption.service");
const cluster_firewall_integration_service_1 = require("./cluster-firewall-integration.service");
const operation_steps_helper_1 = require("../../operations/helpers/operation-steps.helper");
const vnet_subnet_entity_1 = require("../../vnets/entities/vnet-subnet.entity");
const nip_token_util_1 = require("../../../dns/utils/nip-token.util");
const hostname_mode_enum_1 = require("../../../dns/enums/hostname-mode.enum");
/**
 * Service responsible for cluster creation logic
 */
let ClusterCreationService = ClusterCreationService_1 = class ClusterCreationService {
    constructor(clusterRepository, operationRepository, vnetSubnetRepository, infrastructureQueue, encryptionService, clusterFirewallIntegrationService) {
        this.clusterRepository = clusterRepository;
        this.operationRepository = operationRepository;
        this.vnetSubnetRepository = vnetSubnetRepository;
        this.infrastructureQueue = infrastructureQueue;
        this.encryptionService = encryptionService;
        this.clusterFirewallIntegrationService = clusterFirewallIntegrationService;
        this.logger = new common_1.Logger(ClusterCreationService_1.name);
    }
    /**
     * Create a new K3s cluster
     */
    async createCluster(dto) {
        this.logger.log(`Creating cluster: ${dto.name}`);
        // Generate and encrypt K3s token
        const k3sToken = this.encryptionService.generateK3sToken();
        const k3sTokenEncrypted = this.encryptionService.encrypt(k3sToken);
        // Determine cluster type from metadata
        const metadata = dto.metadata || {};
        const clusterType = metadata.isObservabilityCluster
            ? cluster_entity_1.ClusterType.OBSERVABILITY
            : cluster_entity_1.ClusterType.WORKLOAD;
        // Resolve the environment-level VNet/Subnet (seeded at bootstrap by the CLI).
        // Every cluster — observability or workload — joins the same private network
        // so intra-cluster and inter-cluster traffic stays off the public interface.
        const envSubnet = await this.vnetSubnetRepository.findOne({
            where: {},
            order: { createdAt: 'ASC' },
        });
        if (!envSubnet) {
            throw new common_1.BadRequestException('No environment subnet registered. The CLI must provision a VNet/Subnet during `flui env create` before any cluster can be created.');
        }
        metadata.vnetConfig = {
            vnetId: envSubnet.vnetId,
            subnetId: envSubnet.id,
            autoAssignIp: true,
        };
        this.logger.log(`Cluster ${dto.name} attached to environment subnet ${envSubnet.id} (${envSubnet.ipRange})`);
        // Resolve nip.io hostname token: when running in IP mode, every cluster gets
        // a unique token segment so the LE domain set differs between recreations,
        // avoiding the 5-certs-per-7-days rate limit.
        const hostnameMode = dto.endpointHostnameMode ?? hostname_mode_enum_1.HostnameMode.IP;
        let nipHostnameToken = null;
        if (hostnameMode === hostname_mode_enum_1.HostnameMode.IP) {
            if (dto.nipHostnameToken) {
                if (!(0, nip_token_util_1.isValidNipHostnameToken)(dto.nipHostnameToken)) {
                    throw new common_1.BadRequestException('nipHostnameToken must match [a-z0-9-], 1-30 chars, no leading/trailing dash.');
                }
                nipHostnameToken = dto.nipHostnameToken;
            }
            else {
                nipHostnameToken = (0, nip_token_util_1.generateNipHostnameToken)();
            }
            this.logger.log(`Cluster ${dto.name} nip.io hostname token: ${nipHostnameToken}`);
        }
        // Create cluster record
        const cluster = this.clusterRepository.create({
            name: dto.name,
            provider: dto.provider,
            region: dto.region,
            nodeSize: dto.nodeSize,
            nodeCount: 0, // Will be updated as nodes are created
            autoscalingEnabled: dto.autoscalingEnabled || false,
            minNodes: dto.minNodes,
            maxNodes: dto.maxNodes,
            scaleUpMemoryPct: dto.scaleUpMemoryPct,
            scaleUpCpuPct: dto.scaleUpCpuPct,
            cooldownSeconds: dto.cooldownSeconds,
            k3sTokenEncrypted,
            k3sVersion: dto.k3sVersion,
            status: cluster_entity_1.ClusterStatus.CREATING,
            clusterType,
            sshKeyIds: dto.sshKeys,
            image: dto.image,
            diskSizeGb: dto.diskSizeGb,
            endpointHostnameMode: dto.endpointHostnameMode,
            nipHostnameToken,
            // Flui shared storage (§14 of scaling doc). Default enabled for all
            // cluster types unless explicitly disabled via --no-shared-storage.
            // The architecture works on both OBSERVABILITY and WORKLOAD clusters;
            // the cluster-type discriminator is only for the future split where
            // observability gets its own dedicated topology.
            sharedStorageEnabled: dto.sharedStorageEnabled !== false,
            sharedStorageVolumeSizeGb: dto.sharedStorageVolumeSizeGb ?? 20,
            metadata,
        });
        const savedCluster = await this.clusterRepository.save(cluster);
        this.logger.log(`Cluster record created: ${savedCluster.id}`);
        // Create cluster firewall (BEFORE creating nodes).
        // Intra-cluster Prometheus scraping no longer requires public firewall rules:
        // observability ↔ workload metrics traffic flows over the environment VNet.
        let providerFirewallId = null;
        try {
            const desiredRules = dto.firewallRules || [];
            providerFirewallId =
                await this.clusterFirewallIntegrationService.createAndReconcileFirewall(savedCluster, desiredRules);
            this.logger.log(`Firewall created for cluster ${savedCluster.id}: ${providerFirewallId}`);
        }
        catch (error) {
            this.logger.error(`Failed to create firewall for cluster ${savedCluster.id}: ${error.message}`, error.stack);
            // Firewall creation failure should fail cluster creation
            await this.clusterRepository.delete(savedCluster.id);
            throw new common_1.BadRequestException(`Failed to create cluster firewall: ${error.message}`);
        }
        // Use worker count directly from DTO
        const workerCount = dto.workerCount;
        // Generate dynamic steps based on cluster configuration
        const withFirewall = !!providerFirewallId;
        const operationSteps = (0, operation_steps_helper_1.getOperationSteps)(infrastructure_operations_entity_1.OperationType.CREATE_CLUSTER, {
            workerCount,
            withFirewall,
        });
        // Create operation for tracking
        const operation = this.operationRepository.create({
            operationType: infrastructure_operations_entity_1.OperationType.CREATE_CLUSTER,
            status: infrastructure_operations_entity_1.OperationStatus.PENDING,
            resourceType: 'cluster',
            resourceName: dto.name,
            resourceId: savedCluster.id,
            provider: dto.provider,
            totalSteps: operationSteps.length,
            currentStepIndex: 0,
            currentStepProgress: 0,
            metadata: {
                clusterConfig: dto,
                estimatedDurationInSeconds: 900,
                providerFirewallId, // Single firewall ID (not array)
                operationSteps: operationSteps, // Fixed: was 'steps', now 'operationSteps'
            },
        });
        const savedOperation = await this.operationRepository.save(operation);
        // Queue cluster creation job
        const jobData = {
            operationId: savedOperation.id,
            clusterId: savedCluster.id,
        };
        await this.infrastructureQueue.add('create-cluster', jobData, {
            attempts: 3,
            backoff: {
                type: 'exponential',
                delay: 2000,
            },
            timeout: 1800000, // 30 minutes
        });
        this.logger.log(`Cluster creation job queued for cluster ${savedCluster.id} with operation ${savedOperation.id}`);
        return savedOperation;
    }
};
exports.ClusterCreationService = ClusterCreationService;
exports.ClusterCreationService = ClusterCreationService = ClusterCreationService_1 = __decorate([
    (0, common_1.Injectable)(),
    __param(0, (0, typeorm_1.InjectRepository)(cluster_entity_1.ClusterEntity)),
    __param(1, (0, typeorm_1.InjectRepository)(infrastructure_operations_entity_1.InfrastructureOperationEntity)),
    __param(2, (0, typeorm_1.InjectRepository)(vnet_subnet_entity_1.VNetSubnetEntity)),
    __param(3, (0, bull_1.InjectQueue)('infrastructure')),
    __metadata("design:paramtypes", [typeorm_2.Repository,
        typeorm_2.Repository,
        typeorm_2.Repository, Object, encryption_service_1.EncryptionService,
        cluster_firewall_integration_service_1.ClusterFirewallIntegrationService])
], ClusterCreationService);
