"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    var desc = Object.getOwnPropertyDescriptor(m, k);
    if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
      desc = { enumerable: true, get: function() { return m[k]; } };
    }
    Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
    Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
    o["default"] = v;
});
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 __importStar = (this && this.__importStar) || (function () {
    var ownKeys = function(o) {
        ownKeys = Object.getOwnPropertyNames || function (o) {
            var ar = [];
            for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
            return ar;
        };
        return ownKeys(o);
    };
    return function (mod) {
        if (mod && mod.__esModule) return mod;
        var result = {};
        if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
        __setModuleDefault(result, mod);
        return result;
    };
})();
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 CAManagerService_1;
Object.defineProperty(exports, "__esModule", { value: true });
exports.CAManagerService = void 0;
const common_1 = require("@nestjs/common");
const typeorm_1 = require("@nestjs/typeorm");
const typeorm_2 = require("typeorm");
const ca_keypair_entity_1 = require("../entities/ca-keypair.entity");
const ssh_key_generator_service_1 = require("./ssh-key-generator.service");
const key_storage_service_1 = require("./key-storage.service");
const crypto = __importStar(require("node:crypto"));
const node_fs_1 = require("node:fs");
const path = __importStar(require("node:path"));
const os = __importStar(require("node:os"));
let CAManagerService = CAManagerService_1 = class CAManagerService {
    constructor(caRepository, keyGenerator, keyStorage) {
        this.caRepository = caRepository;
        this.keyGenerator = keyGenerator;
        this.keyStorage = keyStorage;
        this.logger = new common_1.Logger(CAManagerService_1.name);
    }
    async initializeCA() {
        throw new common_1.ConflictException('API CA initialization is disabled. SSH CA is managed by the CLI (~/.flui/ca/) ' +
            'and shared with the API via environment variable or file. ' +
            'Use "flui env:create" to set up the SSH CA.');
    }
    async getActiveCA() {
        return await this.caRepository.findOne({
            where: { isActive: true },
            order: { createdAt: 'DESC' },
        });
    }
    /**
     * Resolves the CLI CA directory for the active profile.
     * Priority: FLUI_PROFILE env var > ~/.flui/context file > 'default'
     * Path: ~/.flui/profiles/<profile>/ca/
     */
    getCliCaDir() {
        const baseDir = path.join(os.homedir(), '.flui');
        const profilesDir = path.join(baseDir, 'profiles');
        let profile = process.env.FLUI_PROFILE;
        if (!profile) {
            const contextFile = path.join(baseDir, 'context');
            try {
                profile = (0, node_fs_1.readFileSync)(contextFile, 'utf-8').trim() || 'default';
            }
            catch {
                profile = 'default';
            }
        }
        return path.join(profilesDir, profile, 'ca');
    }
    async getCAPrivateKey(tenantId) {
        // 1. Kubernetes Secret (env var) — production K8s deployment
        const envCaKey = process.env.SSH_CA_PRIVATE_KEY;
        if (envCaKey) {
            this.logger.debug('Using CLI CA private key from environment');
            // SSH private keys MUST end with a newline
            return envCaKey.replaceAll(String.raw `\r\n`, '\n').trimEnd() + '\n';
        }
        // 2. CLI CA file — local development (profile-aware: ~/.flui/profiles/<profile>/ca/ca_key)
        const caKeyPath = path.join(this.getCliCaDir(), 'ca_key');
        try {
            const fileKey = await node_fs_1.promises.readFile(caKeyPath, 'utf-8');
            this.logger.debug(`Using CLI CA private key from ${caKeyPath}`);
            // SSH private keys MUST end with a newline — normalize but preserve it
            return fileKey.replaceAll(String.raw `\r\n`, '\n').trimEnd() + '\n';
        }
        catch {
            // File not found, continue to database fallback
        }
        // 3. Database CA — legacy fallback
        const ca = await this.getActiveCA();
        if (!ca) {
            throw new common_1.NotFoundException(`No active CA found. Set SSH_CA_PRIVATE_KEY env var, place key in ${caKeyPath}, or initialize CA.`);
        }
        if (!ca.encryptedPrivateKey) {
            throw new common_1.NotFoundException('CA private key not available.');
        }
        return this.keyStorage.decryptKeyFromString(ca.encryptedPrivateKey);
    }
    async getCAPublicKey(tenantId) {
        // 1. Env var — production K8s deployment
        const envCaPubKey = process.env.SSH_CA_PUBLIC_KEY;
        if (envCaPubKey)
            return envCaPubKey;
        // 2. CLI CA file — local development (profile-aware: ~/.flui/profiles/<profile>/ca/ca_key.pub)
        const caPubKeyPath = path.join(this.getCliCaDir(), 'ca_key.pub');
        try {
            const filePubKey = await node_fs_1.promises.readFile(caPubKeyPath, 'utf-8');
            this.logger.debug(`Using CLI CA public key from ${caPubKeyPath}`);
            return filePubKey.trim();
        }
        catch {
            // File not found, continue to database fallback
        }
        // 3. Database — legacy fallback
        const ca = await this.getActiveCA();
        if (!ca) {
            throw new common_1.NotFoundException('No active CA found.');
        }
        return ca.publicKey;
    }
    async getCAInfo() {
        const ca = await this.getActiveCA();
        if (!ca) {
            throw new common_1.NotFoundException('No active CA found. Initialize CA first.');
        }
        return ca;
    }
    async getEnrollmentScript(tenantId) {
        // Use default CA for now (no tenantId passed)
        const caPublicKey = await this.getCAPublicKey(tenantId);
        return `#!/bin/bash
set -e

echo "=== Flui.cloud SSH CA Enrollment ==="

# Backup existing sshd_config
cp /etc/ssh/sshd_config /etc/ssh/sshd_config.bak.$(date +%s)

# Install CA public key
echo "${caPublicKey}" > /etc/ssh/trusted_user_ca_keys
chmod 644 /etc/ssh/trusted_user_ca_keys

echo "✓ CA public key installed"

# Configure sshd to trust CA
if ! grep -q "^TrustedUserCAKeys" /etc/ssh/sshd_config; then
  echo "TrustedUserCAKeys /etc/ssh/trusted_user_ca_keys" >> /etc/ssh/sshd_config
  echo "✓ TrustedUserCAKeys configured"
else
  echo "⊙ TrustedUserCAKeys already configured"
fi

# Disable password authentication (security hardening)
sed -i 's/^#\\?PasswordAuthentication.*/PasswordAuthentication no/' /etc/ssh/sshd_config
echo "✓ Password authentication disabled"

# Reload SSH daemon
if systemctl reload sshd 2>/dev/null; then
  echo "✓ SSH daemon reloaded (sshd)"
elif systemctl reload ssh 2>/dev/null; then
  echo "✓ SSH daemon reloaded (ssh)"
else
  echo "⚠ Could not reload SSH daemon. Manual restart may be required."
  exit 1
fi

echo "=== Enrollment completed successfully ==="
`;
    }
    /**
     * Register an external CA public key (from CLI)
     * @param publicKey - SSH CA public key in OpenSSH format
     * @param options - Registration options (name, replace, metadata)
     */
    async registerExternalCA(publicKey, options = {}) {
        this.logger.log('Registering external CA public key...');
        // Validate public key format
        const trimmedKey = publicKey.trim();
        if (!trimmedKey.startsWith('ssh-ed25519 ') &&
            !trimmedKey.startsWith('ssh-rsa ')) {
            throw new common_1.BadRequestException('Invalid public key format. Must start with ssh-ed25519 or ssh-rsa');
        }
        // Extract key type
        const keyType = trimmedKey.startsWith('ssh-ed25519') ? 'ed25519' : 'rsa';
        // Compute fingerprint
        const fingerprint = this.computeFingerprint(trimmedKey, keyType);
        // Check for existing CA
        const existingCA = await this.getActiveCA();
        if (existingCA) {
            // Check if it's the same CA (by fingerprint)
            if (existingCA.fingerprint === fingerprint) {
                throw new common_1.ConflictException(`CA with fingerprint ${fingerprint} is already registered`);
            }
            // If replace flag is not set, throw error
            if (!options.replace) {
                throw new common_1.ConflictException('CA already exists. Use replace=true to replace existing CA.');
            }
            // Deactivate existing CA
            this.logger.log(`Deactivating existing CA: ${existingCA.name}`);
            existingCA.isActive = false;
            await this.caRepository.save(existingCA);
        }
        // Generate name if not provided
        const name = options.name || `flui-ca-${new Date().toISOString().split('T')[0]}`;
        const encryptedPrivateKey = options.privateKey
            ? this.keyStorage.encryptKeyToString(options.privateKey.replaceAll(String.raw `\r\n`, '\n').trimEnd() +
                '\n')
            : null;
        const ca = this.caRepository.create({
            name,
            publicKey: trimmedKey,
            encryptedPrivateKey,
            fingerprint,
            type: keyType,
            isActive: true,
            expiresAt: null,
            // Store metadata if provided
            ...(options.metadata && { metadata: options.metadata }),
        });
        const saved = await this.caRepository.save(ca);
        this.logger.log(`External CA registered: ${saved.name} (${saved.fingerprint})`);
        return saved;
    }
    /**
     * Backfill the encrypted private key on an existing CA row that was
     * originally seeded with only the public half. Used by the bootstrap
     * seeder so once SSH_CA_PRIVATE_KEY appears in the environment the DB
     * row becomes self-sufficient and survives even when the env var is no
     * longer mounted.
     */
    async attachPrivateKey(caId, privateKey) {
        const ca = await this.caRepository.findOne({ where: { id: caId } });
        if (!ca) {
            throw new common_1.NotFoundException(`CA ${caId} not found`);
        }
        ca.encryptedPrivateKey = this.keyStorage.encryptKeyToString(privateKey.replaceAll(String.raw `\r\n`, '\n').trimEnd() + '\n');
        await this.caRepository.save(ca);
        this.logger.log(`CA ${ca.name} private key attached`);
    }
    /**
     * Compute SHA256 fingerprint for a public key
     * @param publicKey - OpenSSH format public key
     * @param keyType - Key type (ed25519 or rsa)
     */
    computeFingerprint(publicKey, keyType) {
        // Extract the base64 part (second field in OpenSSH format)
        const parts = publicKey.trim().split(' ');
        if (parts.length < 2) {
            throw new common_1.BadRequestException('Invalid public key format');
        }
        const keyData = parts[1];
        const keyBuffer = Buffer.from(keyData, 'base64');
        // Compute SHA256 hash
        const hash = crypto.createHash('sha256').update(keyBuffer).digest('base64');
        return `SHA256:${hash.replace(/=+$/, '')}`; // Remove trailing =
    }
};
exports.CAManagerService = CAManagerService;
exports.CAManagerService = CAManagerService = CAManagerService_1 = __decorate([
    (0, common_1.Injectable)(),
    __param(0, (0, typeorm_1.InjectRepository)(ca_keypair_entity_1.CAKeypairEntity)),
    __metadata("design:paramtypes", [typeorm_2.Repository,
        ssh_key_generator_service_1.SSHKeyGeneratorService,
        key_storage_service_1.KeyStorageService])
], CAManagerService);
