"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 AccessService_1;
Object.defineProperty(exports, "__esModule", { value: true });
exports.AccessService = void 0;
const common_1 = require("@nestjs/common");
const typeorm_1 = require("@nestjs/typeorm");
const typeorm_2 = require("typeorm");
const key_storage_service_1 = require("./key-storage.service");
const ssh_key_generator_service_1 = require("./ssh-key-generator.service");
const credentials_entity_1 = require("../../management/entities/credentials.entity");
const credential_purpose_enum_1 = require("../enums/credential-purpose.enum");
const bearerToken_service_1 = require("./bearerToken.service");
const provider_credentials_repository_1 = require("../repositories/provider-credentials.repository");
const jwt_1 = require("@nestjs/jwt");
const api_token_repository_1 = require("../repositories/api-token.repository");
const api_token_mapper_1 = require("../mapper/api-token.mapper");
const ssh_key_mapper_1 = require("../mapper/ssh-key.mapper");
const ssh_provider_factory_1 = require("../providers/ssh-provider.factory");
const provider_factory_1 = require("../../providers/services/provider.factory");
const label_service_1 = require("../../common/services/label.service");
const cluster_entity_1 = require("../../infrastructure/clusters/entities/cluster.entity");
let AccessService = AccessService_1 = class AccessService {
    constructor(repository, keyStorage, keyGenerator, bearerTokenService, providerCredentialsRepository, apiTokenRepository, jwtService, sshProviderFactory, providerFactory, labelService, clusterRepository) {
        this.repository = repository;
        this.keyStorage = keyStorage;
        this.keyGenerator = keyGenerator;
        this.bearerTokenService = bearerTokenService;
        this.providerCredentialsRepository = providerCredentialsRepository;
        this.apiTokenRepository = apiTokenRepository;
        this.jwtService = jwtService;
        this.sshProviderFactory = sshProviderFactory;
        this.providerFactory = providerFactory;
        this.labelService = labelService;
        this.clusterRepository = clusterRepository;
        this.logger = new common_1.Logger(AccessService_1.name);
    }
    async addSSHKey(input) {
        const keyPair = await this.keyGenerator.generateKeyPair(input.type);
        const existing = await this.repository.findKeyByFingerprint(keyPair.fingerprint);
        if (existing) {
            throw new common_1.BadRequestException('SSH key already exists');
        }
        const keyId = Date.now().toString();
        const keyPath = await this.keyStorage.storePrivateKey(input.userName, keyId, keyPair.privateKey);
        // Create SSH key in local database
        const createdKey = await this.repository.createKey(input, {
            ...keyPair,
            privateKey: keyPath,
        }, input.tags);
        // Sync with specified providers or all configured providers
        const syncErrors = await this.syncSSHKeyToAllProviders(createdKey, input.providers);
        // Reload to pick up persisted providerKeyMappings
        const savedKey = await this.repository.findKeyById(createdKey.id);
        const dto = ssh_key_mapper_1.SSHKeyMapper.toDto(savedKey);
        if (Object.keys(syncErrors).length > 0) {
            dto.syncErrors = syncErrors;
        }
        return dto;
    }
    /**
     * Create an SSH key from pre-generated key pair
     * Used for temporary bootstrap keys during cluster creation
     */
    async createSSHKey(input) {
        const existing = await this.repository.findKeyByFingerprint(input.fingerprint);
        if (existing) {
            throw new common_1.BadRequestException('SSH key already exists');
        }
        const keyId = Date.now().toString();
        const keyPath = await this.keyStorage.storePrivateKey('system', // System user for bootstrap keys
        keyId, input.private_key);
        // Create SSH key in local database
        const createdKey = await this.repository.createKey({
            name: input.name,
            userName: 'system',
            type: input.type || 'ed25519',
        }, {
            publicKey: input.public_key,
            privateKey: keyPath,
            fingerprint: input.fingerprint,
        }, input.tags || input.metadata || {}, input.autoGenerated || false);
        // Skip provider sync for temporary keys
        if (!input.temporary) {
            await this.syncSSHKeyToAllProviders(createdKey);
        }
        return createdKey;
    }
    async removeSSHKey(userId, keyId, removeFromProviders = true, provider) {
        const isUuid = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(keyId);
        if (!isUuid) {
            // keyId is a provider key ID (e.g. Hetzner numeric ID)
            const key = await this.repository.findKeyByProviderKeyId(keyId);
            if (key) {
                if (removeFromProviders) {
                    await this.removeSSHKeyFromAllProviders(key);
                }
                await this.keyStorage.deleteKey(key.keyPath);
                await this.repository.deleteKey(key.id);
                return;
            }
            // Key exists only on provider — delete directly
            if (!provider) {
                throw new common_1.BadRequestException('Key not found in database. Provide ?provider= to delete it directly from the cloud provider.');
            }
            await this.removeSSHKeyFromProvider(provider, keyId);
            return;
        }
        const key = await this.repository.findKeyById(keyId);
        if (!key) {
            throw new common_1.NotFoundException('SSH key not found');
        }
        if (removeFromProviders) {
            await this.removeSSHKeyFromAllProviders(key);
        }
        await this.keyStorage.deleteKey(key.keyPath);
        await this.repository.deleteKey(keyId);
    }
    async getPrivateKey(userId, keyId) {
        const key = await this.repository.findKeyById(keyId);
        if (!key) {
            throw new common_1.NotFoundException('SSH key not found');
        }
        return this.keyStorage.retrievePrivateKey(key.keyPath);
    }
    // Bearer Tokens methods (unchanged)
    async generateBearerToken(provider, dto) {
        const token = await this.bearerTokenService.generateToken(provider, dto);
        const password = this.keyStorage.encryptKeyToString(dto.password);
        const client_id = this.keyStorage.encryptKeyToString(dto.client_id);
        const client_secret = this.keyStorage.encryptKeyToString(dto.client_secret);
        const decodedRefresh = this.jwtService.decode(token.refresh_token);
        this.providerCredentialsRepository.saveCredentials(provider, dto.username, password, client_id, client_secret, token.access_token, token.refresh_token, token.expires_in, decodedRefresh?.exp);
        return token;
    }
    async getActiveBearerToken(provider) {
        const providerCredentials = await this.providerCredentialsRepository.findByProvider(provider);
        // If no active credentials found, return not found
        if (!providerCredentials || providerCredentials.length === 0) {
            throw new common_1.NotFoundException('No active credentials found');
        }
        // If multiple active credentials found, return the first one
        const credentials = providerCredentials[0];
        // Check if token is expired, if not return it, otherwise check if refresh token is available and not expired,
        // if yes, refresh token and return new token, otherwise use credentials to generate new token
        if (credentials.token_expires_at &&
            credentials.token_expires_at > new Date()) {
            return {
                access_token: credentials.access_token,
                refresh_token: credentials.refresh_token,
                expires_in: Math.floor((credentials.token_expires_at.getTime() - Date.now()) / 1000),
                token_type: 'Bearer',
            };
        }
        else if (credentials.refresh_token &&
            credentials.refresh_token_expires_at &&
            credentials.refresh_token_expires_at > new Date()) {
            const client_secret = this.keyStorage.decryptKeyFromString(credentials.client_secret);
            const client_id = this.keyStorage.decryptKeyFromString(credentials.client_id);
            const token = await this.refreshBearerToken(provider, {
                provider: provider,
                client_id: client_id,
                client_secret: client_secret,
                refresh_token: credentials.refresh_token,
            });
            return token;
        }
        const password = this.keyStorage.decryptKeyFromString(credentials.password);
        const client_id = this.keyStorage.decryptKeyFromString(credentials.client_id);
        const client_secret = this.keyStorage.decryptKeyFromString(credentials.client_secret);
        return await this.generateBearerToken(provider, {
            provider: provider,
            client_id: client_id,
            client_secret: client_secret,
            username: credentials.username,
            password: password,
            save_credentials: true,
        });
    }
    async refreshBearerToken(provider, dto) {
        try {
            return await this.bearerTokenService.refreshToken(provider, dto);
        }
        catch (error) {
            if (error.status === 400 &&
                error.data?.error === 'invalid_grant' &&
                error.data?.error_description?.includes('Token is not active')) {
                throw new common_1.UnauthorizedException('Your refresh token is no longer active. Please log in again.');
            }
            // Handle other invalid_grant errors
            if (error.status === 400 && error.data?.error === 'invalid_grant') {
                throw new common_1.UnauthorizedException(`Authentication failed: ${error.data?.error_description || 'Invalid token'}. Please log in again.`);
            }
            // Handle generic 400 errors
            if (error.status === 400) {
                throw new common_1.BadRequestException(`Failed to refresh token: ${error.data?.error_description || error.message || 'Bad request'}`);
            }
            // For all other errors, propagate them
            throw error;
        }
    }
    async listBearerTokens() {
        return await this.providerCredentialsRepository.findAll();
    }
    async removeBearerToken(id) {
        return await this.providerCredentialsRepository.deleteTokenAndCredentials(id);
    }
    // API Tokens methods (updated to use mapper)
    /**
     * Retrieves all active API tokens
     * @returns List of API tokens as DTOs
     */
    async listApiTokens() {
        const tokens = await this.apiTokenRepository.findAll();
        return api_token_mapper_1.ApiTokenMapper.toDtoList(tokens);
    }
    /**
     * Creates a new API token
     * @param createApiTokenDto Data for token creation
     * @returns The created token as DTO
     */
    async createApiToken(createApiTokenDto) {
        const encryptedToken = this.keyStorage.encryptKeyToString(createApiTokenDto.token);
        const newToken = await this.apiTokenRepository.createToken(createApiTokenDto.provider.toLowerCase(), createApiTokenDto.label, createApiTokenDto.notes, encryptedToken, {
            credentialType: credentials_entity_1.CredentialType.API_KEY,
            expiresAt: createApiTokenDto.expiresAt,
        });
        return api_token_mapper_1.ApiTokenMapper.toDto(newToken);
    }
    async createAccessKeyPair(dto) {
        const encryptedSecretKey = this.keyStorage.encryptKeyToString(dto.secretKey);
        const encryptedAccessKey = this.keyStorage.encryptKeyToString(dto.accessKey);
        const newToken = await this.apiTokenRepository.createToken(dto.provider, dto.label, dto.notes ?? null, encryptedSecretKey, {
            credentialType: credentials_entity_1.CredentialType.ACCESS_KEY_SECRET,
            encryptedAccessKey,
            expiresAt: dto.expiresAt,
        });
        return api_token_mapper_1.ApiTokenMapper.toDto(newToken);
    }
    async getActiveAccessKeyPair(provider, purpose = credential_purpose_enum_1.CredentialPurpose.COMPUTE) {
        const tokens = await this.apiTokenRepository.findByProviderAndPurpose(provider, purpose);
        const token = tokens?.find((t) => t.credential_type === credentials_entity_1.CredentialType.ACCESS_KEY_SECRET);
        if (!token) {
            throw new common_1.NotFoundException(`No active access key pair found for provider ${provider}`);
        }
        const secretKey = this.keyStorage.decryptKeyFromString(token.encrypted_token);
        const accessKey = token.encrypted_access_key
            ? this.keyStorage.decryptKeyFromString(token.encrypted_access_key)
            : '';
        return { accessKey, secretKey };
    }
    async getActiveCredentialMetadata(provider, purpose = credential_purpose_enum_1.CredentialPurpose.COMPUTE) {
        const tokens = await this.apiTokenRepository.findByProviderAndPurpose(provider, purpose);
        if (!tokens || tokens.length === 0)
            return null;
        const token = tokens[0];
        return {
            id: token.id,
            credentialType: token.credential_type,
            expiresAt: token.expires_at ?? null,
        };
    }
    async updateActiveCredentialExpiry(provider, expiresAt, purpose = credential_purpose_enum_1.CredentialPurpose.COMPUTE) {
        const meta = await this.getActiveCredentialMetadata(provider, purpose);
        if (!meta) {
            throw new common_1.NotFoundException(`No active credential found for provider ${provider}`);
        }
        await this.apiTokenRepository.updateExpiry(meta.id, expiresAt);
    }
    async deleteActiveProviderCredentials(provider, purpose = credential_purpose_enum_1.CredentialPurpose.COMPUTE) {
        const tokens = await this.apiTokenRepository.findByProviderAndPurpose(provider, purpose);
        for (const token of tokens ?? []) {
            await this.apiTokenRepository.deleteToken(token.id);
        }
    }
    /**
     * Retrieves and decrypts an API token
     * @param id ID of the token to retrieve
     * @returns The decrypted token value
     */
    async getDecryptApiToken(id) {
        try {
            // Retrieve the encrypted token
            const encryptedToken = await this.apiTokenRepository.getEncryptedToken(id);
            // Decrypt the token before returning it
            return this.keyStorage.decryptKeyFromString(encryptedToken);
        }
        catch {
            throw new common_1.NotFoundException(`API token with ID ${id} not found`);
        }
    }
    /**
     * Retrieves information about an API token
     * @param id ID of the token to retrieve
     * @returns Token information as DTO
     */
    async getApiToken(id) {
        try {
            const token = await this.apiTokenRepository.findById(id);
            if (!token) {
                throw new common_1.NotFoundException(`API token with ID ${id} not found`);
            }
            return api_token_mapper_1.ApiTokenMapper.toDto(token);
        }
        catch {
            throw new common_1.NotFoundException(`API token with ID ${id} not found`);
        }
    }
    // get active api token by provider and decrypt it
    async getActiveApiToken(provider, purpose = credential_purpose_enum_1.CredentialPurpose.COMPUTE) {
        const tokens = await this.apiTokenRepository.findByProviderAndPurpose(provider, purpose);
        if (!tokens || tokens.length === 0) {
            throw new common_1.NotFoundException('No active API token found');
        }
        const token = tokens[0];
        return await this.getDecryptApiToken(token.id);
    }
    /**
     * Deletes an API token
     * @param id ID of the token to delete
     * @returns Confirmation message
     */
    async removeApiToken(id) {
        try {
            await this.apiTokenRepository.deleteToken(id);
            return { success: true, message: 'API token removed successfully' };
        }
        catch {
            throw new common_1.NotFoundException(`API token with ID ${id} not found`);
        }
    }
    /**
     * Retrieves all API tokens for a specific provider
     * @param provider Provider of tokens to retrieve
     * @returns List of API tokens as DTOs
     */
    async getApiTokensByProvider(provider) {
        const tokens = await this.apiTokenRepository.findByProvider(provider);
        return api_token_mapper_1.ApiTokenMapper.toDtoList(tokens);
    }
    /**
     * Updates the last used timestamp of a token
     * @param id ID of the token to update
     */
    async updateApiTokenLastUsed(id) {
        try {
            await this.apiTokenRepository.updateLastUsed(id);
        }
        catch {
            throw new common_1.NotFoundException(`API token with ID ${id} not found`);
        }
    }
    async listSSHKeys(provider, tagFilters) {
        // Get SSH keys from local database with optional tag filters
        const localKeys = await this.repository.findAllKeys(tagFilters);
        const localKeysDto = ssh_key_mapper_1.SSHKeyMapper.toDtoList(localKeys);
        // If no provider specified, return only local keys
        if (!provider) {
            return localKeysDto.map((key) => ({
                ...key,
                source: 'local',
            }));
        }
        // Get SSH keys from cloud provider
        const providerKeys = await this.getSSHKeysFromProvider(provider);
        // Combine and deduplicate keys
        const combinedKeys = this.combineSSHKeys(localKeysDto, providerKeys, provider);
        return combinedKeys;
    }
    async getSSHKeysFromProvider(provider) {
        try {
            const providerService = this.providerFactory.getProvider(provider);
            // Check if provider supports SSH key listing
            if (!providerService.listSSHKeys) {
                this.logger.warn(`Provider ${provider} does not support SSH key listing`);
                return [];
            }
            return await providerService.listSSHKeys();
        }
        catch (error) {
            this.logger.error(`Failed to fetch SSH keys from ${provider}:`, error.message);
            return [];
        }
    }
    combineSSHKeys(localKeys, providerKeys, provider) {
        const combinedKeys = [];
        // Add local keys with source marker
        localKeys.forEach((key) => {
            combinedKeys.push({
                ...key,
                source: 'local',
            });
        });
        // Add provider keys with source marker and deduplication
        providerKeys.forEach((providerKey) => {
            // Check if this key already exists in local keys (by fingerprint)
            const existsLocally = localKeys.some((localKey) => localKey.fingerprint === providerKey.fingerprint);
            if (existsLocally) {
                const localKeyIndex = combinedKeys.findIndex((key) => key.fingerprint === providerKey.fingerprint &&
                    key.source === 'local');
                if (localKeyIndex !== -1) {
                    combinedKeys[localKeyIndex] = {
                        ...combinedKeys[localKeyIndex],
                        availableOnProvider: provider,
                        providerKeyId: providerKey.id,
                    };
                }
            }
            else {
                combinedKeys.push({
                    ...providerKey,
                    source: provider,
                    syncedFromProvider: true,
                });
            }
        });
        return combinedKeys;
    }
    async updateSSHKey(keyId, updateDto) {
        // Check if key exists
        const existingKey = await this.repository.findKeyById(keyId);
        if (!existingKey) {
            throw new common_1.NotFoundException(`SSH key with ID ${keyId} not found`);
        }
        // Prepare updates
        const updates = {};
        if (updateDto.tags !== undefined) {
            // Complete replacement of tags
            updates.tags = updateDto.tags;
        }
        if (updateDto.providerKeyMappings !== undefined) {
            updates.providerKeyMappings = updateDto.providerKeyMappings;
        }
        // Unsync from specified providers if requested
        if (updateDto.unsyncProviders && updateDto.unsyncProviders.length > 0) {
            const unsyncErrors = await this.unsyncSSHKeyFromProviders(existingKey, updateDto.unsyncProviders);
            const updated = await this.repository.findKeyById(keyId);
            const dto = ssh_key_mapper_1.SSHKeyMapper.toDto(updated);
            if (Object.keys(unsyncErrors).length > 0) {
                dto.syncErrors = unsyncErrors;
            }
            return dto;
        }
        // Sync to specified providers if requested
        if (updateDto.syncProviders && updateDto.syncProviders.length > 0) {
            const syncErrors = await this.syncSSHKeyToAllProviders(existingKey, updateDto.syncProviders);
            const synced = await this.repository.findKeyById(keyId);
            const dto = ssh_key_mapper_1.SSHKeyMapper.toDto(synced);
            if (Object.keys(syncErrors).length > 0) {
                dto.syncErrors = syncErrors;
            }
            return dto;
        }
        // Update the key
        const updatedKey = await this.repository.updateKey(keyId, updates);
        if (!updatedKey) {
            throw new common_1.NotFoundException(`Failed to update SSH key with ID ${keyId}`);
        }
        // Return mapped DTO
        return ssh_key_mapper_1.SSHKeyMapper.toDto(updatedKey);
    }
    /**
     * Sync SSH key to all configured providers (or a specific subset).
     * Returns a map of provider → error message for any providers that failed.
     * Providers that succeed are persisted in providerKeyMappings before returning.
     */
    async syncSSHKeyToAllProviders(sshKey, targetProviders) {
        const providers = targetProviders && targetProviders.length > 0
            ? targetProviders
            : this.providerFactory.getSupportedProviders();
        const providerKeyMappings = sshKey.providerKeyMappings || {};
        const syncErrors = {};
        for (const provider of providers) {
            try {
                const providerKeyId = await this.syncSSHKeyWithProvider(sshKey, provider);
                if (providerKeyId) {
                    providerKeyMappings[provider] = providerKeyId;
                }
            }
            catch (error) {
                const detail = error?.response?.data
                    ? JSON.stringify(error.response.data)
                    : error.message;
                this.logger.warn(`Failed to sync SSH key ${sshKey.id} with ${provider}: ${error.message} — ${detail}`);
                syncErrors[provider] = error.message;
            }
        }
        // Persist mappings for providers that succeeded
        if (Object.keys(providerKeyMappings).length > 0) {
            await this.repository.updateKey(sshKey.id, { providerKeyMappings });
        }
        return syncErrors;
    }
    /**
     * Sync SSH key with a specific provider
     */
    async syncSSHKeyWithProvider(sshKey, provider) {
        try {
            const providerService = this.providerFactory.getProvider(provider);
            // Check if provider supports SSH key creation
            if (!providerService.createSSHKey) {
                this.logger.debug(`Provider ${provider} does not support SSH key creation`);
                return null;
            }
            // Generate labels for the SSH key
            const labels = this.labelService.toRecord(this.labelService.generateSSHKeyLabels({
                sshKeyId: sshKey.id,
                sshKeyName: sshKey.name,
            }));
            // Create SSH key on provider
            const result = await providerService.createSSHKey(sshKey.name, sshKey.publicKey, labels);
            this.logger.log(`SSH key ${sshKey.name} synced with ${provider}, ID: ${result.id}`);
            return result.id;
        }
        catch (error) {
            // If key already exists, try to find it by name/fingerprint
            if (error.message?.includes('already exists')) {
                this.logger.debug(`SSH key ${sshKey.name} may already exist on ${provider}, attempting to find it`);
                // Try to list keys and find matching one
                const providerService = this.providerFactory.getProvider(provider);
                if (providerService.listSSHKeys) {
                    const keys = await providerService.listSSHKeys();
                    const matchingKey = keys.find((k) => k.fingerprint === sshKey.fingerprint || k.name === sshKey.name);
                    if (matchingKey) {
                        this.logger.log(`Found existing SSH key ${sshKey.name} on ${provider} with ID: ${matchingKey.id}`);
                        return matchingKey.id;
                    }
                }
            }
            throw error;
        }
    }
    /**
     * Sync SSH key with a specific provider using custom labels
     * Used for bootstrap keys that need cluster-specific labels
     */
    async syncSSHKeyWithProviderAndLabels(sshKeyId, provider, customLabels) {
        const sshKey = await this.repository.findKeyById(sshKeyId);
        if (!sshKey) {
            throw new common_1.NotFoundException(`SSH key with ID ${sshKeyId} not found`);
        }
        try {
            const providerService = this.providerFactory.getProvider(provider);
            // Check if provider supports SSH key creation
            if (!providerService.createSSHKey) {
                this.logger.debug(`Provider ${provider} does not support SSH key creation`);
                return null;
            }
            // Merge default labels with custom labels
            const defaultLabels = this.labelService.toRecord(this.labelService.generateSSHKeyLabels({
                sshKeyId: sshKey.id,
                sshKeyName: sshKey.name,
            }));
            const finalLabels = {
                ...defaultLabels,
                ...customLabels, // Custom labels override defaults
            };
            // Create SSH key on provider with merged labels
            const result = await providerService.createSSHKey(sshKey.name, sshKey.publicKey, finalLabels);
            this.logger.log(`SSH key ${sshKey.name} synced with ${provider} using custom labels, ID: ${result.id}`);
            // Update providerKeyMappings
            const providerKeyMappings = sshKey.providerKeyMappings || {};
            providerKeyMappings[provider] = result.id;
            await this.repository.updateKey(sshKeyId, { providerKeyMappings });
            return result.id;
        }
        catch (error) {
            this.logger.error(`Failed to sync SSH key ${sshKeyId} with ${provider}: ${error.message}`);
            throw error;
        }
    }
    /**
     * Remove SSH key from specific providers without deleting it locally.
     * Returns a map of provider → error message for any that failed.
     */
    async unsyncSSHKeyFromProviders(sshKey, providers) {
        const providerKeyMappings = {
            ...sshKey.providerKeyMappings,
        };
        const errors = {};
        for (const provider of providers) {
            const providerKeyId = providerKeyMappings[provider];
            if (!providerKeyId) {
                this.logger.debug(`SSH key ${sshKey.id} has no mapping for ${provider}, skipping`);
                continue;
            }
            try {
                await this.removeSSHKeyFromProvider(provider, providerKeyId);
                delete providerKeyMappings[provider];
            }
            catch (error) {
                this.logger.warn(`Failed to remove SSH key ${sshKey.id} from ${provider}: ${error.message}`);
                errors[provider] = error.message;
            }
        }
        await this.repository.updateKey(sshKey.id, { providerKeyMappings });
        return errors;
    }
    /**
     * Remove SSH key from all providers
     */
    async removeSSHKeyFromAllProviders(sshKey) {
        if (!sshKey.providerKeyMappings) {
            return;
        }
        for (const [provider, providerKeyId] of Object.entries(sshKey.providerKeyMappings)) {
            try {
                await this.removeSSHKeyFromProvider(provider, providerKeyId);
            }
            catch (error) {
                this.logger.warn(`Failed to remove SSH key from ${provider}: ${error.message}`);
                // Continue with other providers
            }
        }
    }
    /**
     * Remove SSH key from a specific provider
     */
    async removeSSHKeyFromProvider(provider, providerKeyId) {
        try {
            const providerService = this.providerFactory.getProvider(provider);
            if (!providerService.deleteSSHKey) {
                this.logger.debug(`Provider ${provider} does not support SSH key deletion`);
                return;
            }
            await providerService.deleteSSHKey(providerKeyId);
            this.logger.log(`SSH key ${providerKeyId} removed from ${provider}`);
        }
        catch (error) {
            this.logger.error(`Failed to remove SSH key ${providerKeyId} from ${provider}:`, error.message);
            throw error;
        }
    }
    /**
     * Load SSH keys from the local DB and return them as SshKeyInfo[],
     * including any already-cached provider key ID.
     * After the provider resolves them, call saveSSHKeyProviderMappings() to persist.
     */
    async getSSHKeysAsProviderInfo(sshKeyIds, provider) {
        const entities = [];
        const infos = [];
        for (const id of sshKeyIds) {
            const key = await this.repository.findKeyById(id);
            if (!key) {
                throw new common_1.NotFoundException(`SSH key with ID ${id} not found`);
            }
            entities.push(key);
            infos.push({
                fluiId: key.id,
                name: key.name,
                publicKey: key.publicKey,
                fingerprint: key.fingerprint,
                existingProviderId: key.providerKeyMappings?.[provider] ?? undefined,
            });
        }
        return { infos, entities };
    }
    /**
     * Persist resolved provider key IDs back to the local DB.
     */
    async saveSSHKeyProviderMappings(entities, providerIds, provider) {
        for (let i = 0; i < entities.length; i++) {
            const key = entities[i];
            const providerKeyId = providerIds[i];
            if (!key.providerKeyMappings?.[provider]) {
                const mappings = {
                    ...key.providerKeyMappings,
                    [provider]: providerKeyId,
                };
                await this.repository.updateKey(key.id, {
                    providerKeyMappings: mappings,
                });
            }
        }
    }
    /**
     * Retrieve and decrypt the bootstrap private key for a cluster.
     * First checks cluster.bootstrapKeyId, then falls back to searching
     * by cluster-id/purpose/node-type tags (master node bootstrap key).
     * Used as a fallback SSH auth method when the CA is not yet configured on the server.
     */
    async getBootstrapPrivateKeyForCluster(clusterId) {
        // First try: cluster-level bootstrapKeyId field
        const cluster = await this.clusterRepository.findOneBy({ id: clusterId });
        if (cluster?.bootstrapKeyId) {
            const keyEntity = await this.repository.findKeyById(cluster.bootstrapKeyId);
            if (keyEntity) {
                return this.keyStorage.retrievePrivateKey(keyEntity.keyPath);
            }
        }
        // Second try: find master node bootstrap key by tags
        const keys = await this.repository.findAllKeys({
            'cluster-id': clusterId,
            purpose: 'bootstrap',
            'node-type': 'master',
        });
        if (!keys.length) {
            throw new common_1.NotFoundException(`No bootstrap key found for cluster ${clusterId}`);
        }
        // Use the most recently created one
        const sortedKeys = [...keys].sort((a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime());
        const latest = sortedKeys[0];
        return this.keyStorage.retrievePrivateKey(latest.keyPath);
    }
};
exports.AccessService = AccessService;
exports.AccessService = AccessService = AccessService_1 = __decorate([
    (0, common_1.Injectable)(),
    __param(0, (0, common_1.Inject)('IAccessRepository')),
    __param(10, (0, typeorm_1.InjectRepository)(cluster_entity_1.ClusterEntity)),
    __metadata("design:paramtypes", [Object, key_storage_service_1.KeyStorageService,
        ssh_key_generator_service_1.SSHKeyGeneratorService,
        bearerToken_service_1.BearerTokenService,
        provider_credentials_repository_1.ProviderCredentialsRepository,
        api_token_repository_1.ApiTokenRepository,
        jwt_1.JwtService,
        ssh_provider_factory_1.SSHProviderFactory,
        provider_factory_1.ProviderFactory,
        label_service_1.LabelService,
        typeorm_2.Repository])
], AccessService);
