"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 FirewallDesiredStateService_1;
Object.defineProperty(exports, "__esModule", { value: true });
exports.FirewallDesiredStateService = void 0;
const common_1 = require("@nestjs/common");
const typeorm_1 = require("@nestjs/typeorm");
const typeorm_2 = require("typeorm");
const node_crypto_1 = require("node:crypto");
const cluster_firewall_entity_1 = require("../entities/cluster-firewall.entity");
const cluster_firewall_dto_1 = require("../dto/cluster-firewall.dto");
const cluster_entity_1 = require("../../clusters/entities/cluster.entity");
const cluster_node_entity_1 = require("../../clusters/entities/cluster-node.entity");
let FirewallDesiredStateService = FirewallDesiredStateService_1 = class FirewallDesiredStateService {
    constructor(firewallRepository) {
        this.firewallRepository = firewallRepository;
        this.logger = new common_1.Logger(FirewallDesiredStateService_1.name);
    }
    /**
     * Create a new firewall for a cluster
     */
    async createFirewall(clusterId, desiredRules) {
        this.logger.log(`Creating firewall for cluster ${clusterId}`);
        // Check if firewall already exists for this cluster
        const existing = await this.firewallRepository.findOne({
            where: { clusterId },
        });
        if (existing) {
            throw new common_1.ConflictException(`Firewall already exists for cluster ${clusterId}`);
        }
        const canonicalRules = this.canonicalizeRules(desiredRules);
        const desiredHash = this.calculateHash(canonicalRules);
        const firewall = this.firewallRepository.create({
            clusterId,
            desiredRules: canonicalRules,
            desiredHash,
            reconciliationStatus: cluster_firewall_entity_1.ReconciliationStatus.PENDING,
        });
        return await this.firewallRepository.save(firewall);
    }
    /**
     * Get firewall by ID
     */
    async getFirewallById(id) {
        const firewall = await this.firewallRepository.findOne({
            where: { id },
            relations: ['cluster', 'cluster.nodes'],
        });
        if (!firewall) {
            throw new common_1.NotFoundException(`Firewall with ID ${id} not found`);
        }
        return firewall;
    }
    /**
     * Get firewall by cluster ID
     */
    async getFirewallByClusterId(clusterId) {
        const firewall = await this.firewallRepository.findOne({
            where: { clusterId },
            relations: ['cluster', 'cluster.nodes'],
        });
        if (!firewall) {
            throw new common_1.NotFoundException(`Firewall for cluster ${clusterId} not found`);
        }
        return firewall;
    }
    /**
     * List all firewalls with optional filters
     */
    async listFirewalls(filters) {
        const queryBuilder = this.firewallRepository
            .createQueryBuilder('firewall')
            .leftJoinAndSelect('firewall.cluster', 'cluster')
            .leftJoinAndSelect('cluster.nodes', 'nodes');
        if (filters?.clusterId) {
            queryBuilder.andWhere('firewall.clusterId = :clusterId', {
                clusterId: filters.clusterId,
            });
        }
        if (filters?.status) {
            queryBuilder.andWhere('firewall.reconciliationStatus = :status', {
                status: filters.status,
            });
        }
        return await queryBuilder.getMany();
    }
    /**
     * Set desired rules for a firewall
     */
    async setDesiredRules(firewallId, desiredRules) {
        const firewall = await this.getFirewallById(firewallId);
        const canonicalRules = this.canonicalizeRules(desiredRules);
        const newDesiredHash = this.calculateHash(canonicalRules);
        // If hash matches, no change needed
        if (newDesiredHash === firewall.desiredHash) {
            this.logger.log(`No changes to desired rules for firewall ${firewallId}`);
            return firewall;
        }
        firewall.desiredRules = canonicalRules;
        firewall.desiredHash = newDesiredHash;
        // If hash doesn't match last applied, mark as drift
        if (newDesiredHash !== firewall.lastAppliedHash) {
            firewall.reconciliationStatus = cluster_firewall_entity_1.ReconciliationStatus.DRIFT;
            this.logger.log(`Firewall ${firewallId} marked as DRIFT`);
        }
        return await this.firewallRepository.save(firewall);
    }
    /**
     * Update firewall reconciliation status
     */
    async updateReconciliationStatus(firewallId, status, errorMessage) {
        const firewall = await this.getFirewallById(firewallId);
        firewall.reconciliationStatus = status;
        firewall.errorMessage = errorMessage || null;
        if (status === cluster_firewall_entity_1.ReconciliationStatus.IN_SYNC ||
            status === cluster_firewall_entity_1.ReconciliationStatus.ERROR) {
            firewall.lastReconciliationAt = new Date();
        }
        return await this.firewallRepository.save(firewall);
    }
    /**
     * Mark reconciliation complete and update applied state
     */
    async markReconciliationComplete(firewallId, appliedRules, providerFirewallId) {
        const firewall = await this.getFirewallById(firewallId);
        const canonicalAppliedRules = this.canonicalizeRules(appliedRules);
        const appliedHash = this.calculateHash(canonicalAppliedRules);
        firewall.lastAppliedRules = canonicalAppliedRules;
        firewall.lastAppliedHash = appliedHash;
        firewall.lastReconciliationAt = new Date();
        firewall.errorMessage = null;
        if (providerFirewallId) {
            firewall.providerFirewallId = providerFirewallId;
        }
        // Determine status based on hash comparison
        if (appliedHash === firewall.desiredHash) {
            firewall.reconciliationStatus = cluster_firewall_entity_1.ReconciliationStatus.IN_SYNC;
        }
        else {
            firewall.reconciliationStatus = cluster_firewall_entity_1.ReconciliationStatus.DRIFT;
        }
        return await this.firewallRepository.save(firewall);
    }
    /**
     * Update both desired and applied state atomically after successful provider update
     * Used by updateAndApplyRules flow
     */
    async updateDesiredAndAppliedState(firewall, rules, providerFirewallId) {
        const canonicalRules = this.canonicalizeRules(rules);
        const hash = this.calculateHash(canonicalRules);
        firewall.desiredRules = canonicalRules;
        firewall.desiredHash = hash;
        firewall.lastAppliedRules = canonicalRules;
        firewall.lastAppliedHash = hash;
        firewall.lastReconciliationAt = new Date();
        firewall.reconciliationStatus = cluster_firewall_entity_1.ReconciliationStatus.IN_SYNC;
        firewall.errorMessage = null;
        if (providerFirewallId) {
            firewall.providerFirewallId = providerFirewallId;
        }
        return await this.firewallRepository.save(firewall);
    }
    /**
     * Detect drift between desired and last applied state
     */
    async detectDrift(firewallId) {
        const firewall = await this.getFirewallById(firewallId);
        if (!firewall.lastAppliedHash) {
            return true; // Never reconciled
        }
        return firewall.desiredHash !== firewall.lastAppliedHash;
    }
    /**
     * Get reconciliation status
     */
    async getReconciliationStatus(firewallId) {
        const firewall = await this.getFirewallById(firewallId);
        const hasDrift = await this.detectDrift(firewallId);
        return {
            status: firewall.reconciliationStatus,
            hasDrift,
            lastReconciliationAt: firewall.lastReconciliationAt,
            errorMessage: firewall.errorMessage,
            desiredHash: firewall.desiredHash,
            lastAppliedHash: firewall.lastAppliedHash,
        };
    }
    /**
     * Delete a firewall
     */
    async deleteFirewall(firewallId) {
        const firewall = await this.getFirewallById(firewallId);
        await this.firewallRepository.remove(firewall);
        this.logger.log(`Deleted firewall ${firewallId}`);
    }
    /**
     * Calculate canonical hash of rules
     */
    calculateHash(rules) {
        const canonical = this.canonicalizeRules(rules);
        const json = JSON.stringify(canonical);
        return (0, node_crypto_1.createHash)('sha256').update(json).digest('hex');
    }
    /**
     * Canonicalize rules for consistent hashing and comparison
     * - Sorts rules by direction, protocol, port, description
     * - Sorts source/destination IPs
     * - Normalizes protocol to lowercase
     */
    canonicalizeRules(rules) {
        if (!rules || rules.length === 0) {
            return [];
        }
        return rules
            .map((rule) => ({
            description: rule.description || '',
            direction: rule.direction,
            protocol: rule.protocol.toLowerCase(),
            port: rule.port || null,
            sourceIps: rule.sourceIps
                ? [...rule.sourceIps].sort((a, b) => a.localeCompare(b))
                : undefined,
            destinationIps: rule.destinationIps
                ? [...rule.destinationIps].sort((a, b) => a.localeCompare(b))
                : undefined,
        }))
            .sort((a, b) => {
            // Sort by: direction, protocol, port, description
            if (a.direction !== b.direction) {
                return a.direction.localeCompare(b.direction);
            }
            if (a.protocol !== b.protocol) {
                return a.protocol.localeCompare(b.protocol);
            }
            const portA = a.port || '';
            const portB = b.port || '';
            if (portA !== portB) {
                return portA.localeCompare(portB);
            }
            return a.description.localeCompare(b.description);
        });
    }
    /**
     * Convert entity to DTO
     */
    toResponseDto(firewall) {
        const hasDrift = firewall.desiredHash !== firewall.lastAppliedHash;
        const { coverageStatus, clusterInfo } = this.computeCoverage(firewall);
        return new cluster_firewall_dto_1.FirewallResponseDto({
            id: firewall.id,
            clusterId: firewall.clusterId,
            providerFirewallId: firewall.providerFirewallId,
            desiredRules: firewall.desiredRules,
            lastAppliedRules: firewall.lastAppliedRules,
            desiredHash: firewall.desiredHash,
            lastAppliedHash: firewall.lastAppliedHash,
            reconciliationStatus: firewall.reconciliationStatus,
            hasDrift,
            coverageStatus,
            clusterInfo,
            lastReconciliationAt: firewall.lastReconciliationAt,
            errorMessage: firewall.errorMessage,
            metadata: firewall.metadata,
            createdAt: firewall.createdAt,
            updatedAt: firewall.updatedAt,
        });
    }
    /**
     * Compute firewall coverage status from cluster node state (no provider calls)
     */
    computeCoverage(firewall) {
        const cluster = firewall.cluster;
        if (!cluster) {
            return { coverageStatus: cluster_firewall_dto_1.FirewallCoverageStatus.UNKNOWN };
        }
        const orphanedClusterStatuses = [
            cluster_entity_1.ClusterStatus.DELETED,
            cluster_entity_1.ClusterStatus.DELETING,
        ];
        const nodes = cluster.nodes || [];
        const totalNodes = nodes.length;
        const readyNodes = nodes.filter((n) => n.status === cluster_node_entity_1.NodeStatus.READY).length;
        const nodeInfos = nodes.map((n) => ({
            nodeId: n.id,
            serverName: n.serverName,
            nodeType: n.nodeType,
            status: n.status,
            ipAddress: n.ipAddress,
        }));
        const clusterInfo = {
            clusterName: cluster.name,
            clusterStatus: cluster.status,
            totalNodes,
            readyNodes,
            nodes: nodeInfos,
        };
        let coverageStatus;
        if (orphanedClusterStatuses.includes(cluster.status)) {
            coverageStatus = cluster_firewall_dto_1.FirewallCoverageStatus.ORPHANED;
        }
        else if (totalNodes === 0) {
            coverageStatus = cluster_firewall_dto_1.FirewallCoverageStatus.ORPHANED;
        }
        else if (readyNodes === totalNodes) {
            coverageStatus = cluster_firewall_dto_1.FirewallCoverageStatus.FULL;
        }
        else {
            coverageStatus = cluster_firewall_dto_1.FirewallCoverageStatus.PARTIAL;
        }
        return { coverageStatus, clusterInfo };
    }
};
exports.FirewallDesiredStateService = FirewallDesiredStateService;
exports.FirewallDesiredStateService = FirewallDesiredStateService = FirewallDesiredStateService_1 = __decorate([
    (0, common_1.Injectable)(),
    __param(0, (0, typeorm_1.InjectRepository)(cluster_firewall_entity_1.ClusterFirewallEntity)),
    __metadata("design:paramtypes", [typeorm_2.Repository])
], FirewallDesiredStateService);
