import { readFile, writeFile } from 'fs/promises';
import { join } from 'path';
import { homedir, hostname, type } from 'os';
import { createCipheriv, createDecipheriv, scryptSync, randomBytes } from 'crypto';
import { Entry } from '@napi-rs/keyring'
import { jwtDecode } from 'jwt-decode';

const CONFIG_PATH = join(homedir(), '.rainbow-cli-config.json');
const SECRETS_PATH = join(homedir(), '.rainbow-cli-secrets.enc');
const SERVICE_NAME = 'ALE_Rainbow_CLI';

// --- Chiffrement du fichier de fallback secrets ---
let _cachedKey = null;
function _deriveKey() {
    if (_cachedKey) return _cachedKey;
    const user = process.env.USER || process.env.USERNAME || 'default';
    let host;
    try { host = hostname(); } catch { host = 'localhost'; }
    _cachedKey = scryptSync(`${user}@${host}`, 'rainbow-cli-v1', 32);
    return _cachedKey;
}

function _encryptData(data) {
    const key = _deriveKey();
    const iv = randomBytes(12);
    const cipher = createCipheriv('aes-256-gcm', key, iv);
    const enc = Buffer.concat([cipher.update(JSON.stringify(data), 'utf8'), cipher.final()]);
    return JSON.stringify({ v: 1, iv: iv.toString('hex'), tag: cipher.getAuthTag().toString('hex'), data: enc.toString('hex') });
}

function _decryptData(str) {
    try {
        const { iv, tag, data } = JSON.parse(str);
        const key = _deriveKey();
        const decipher = createDecipheriv('aes-256-gcm', key, Buffer.from(iv, 'hex'));
        decipher.setAuthTag(Buffer.from(tag, 'hex'));
        const dec = Buffer.concat([decipher.update(Buffer.from(data, 'hex')), decipher.final()]);
        return JSON.parse(dec.toString('utf8'));
    } catch { return {}; }
}

export class ConfigManager {
    constructor(program, logger, services) {
        this.logger = logger;
        this.services = services;
        services['configManager'] = this;

        this.logger.debug('ConfigManager.constructor');

        this.config = { currentProfile: 'default', profiles: {} };
        this._secretsCache = {}; // fallback quand le keyring est indisponible
        this._keyringWarnShown = false;

        const configCommand = program
            .command('config')
            .description('Manage CLI configuration')
            .action(async () => {
                console.log("Current Profile:", this.config.currentProfile);
                console.log("Available Profiles:", Object.keys(this.config.profiles).join(', '));
            });

        configCommand
            .command('set-profile <name>')
            .description('Set the current profile')
            .action(async (name) => {
                if (!this.config.profiles[name]) {
                    console.error(`Profile ${name} does not exist.`);
                    return;
                }
                this.config.currentProfile = name;
                await this.save();
                console.log(`Current profile set to ${name}`);
            });

        configCommand
            .command('show-profile [name]')
            .description('Show details of a profile (default: current profile)')
            .action(async (name) => {
                const profileName = name ?? this.config.currentProfile;
                const profile = this.config.profiles[profileName];
                if (!profile) {
                    console.error(`Profile ${profileName} does not exist.`);
                    return;
                }
                console.log(`Profile: ${profileName}`);
                const display = { ...profile };
                if (display.proxy?.username) {
                    display.proxy = { ...display.proxy, password: '********' };
                }
                console.log(display);
            });

        configCommand
            .command('create-profile')
            .description('Create a new profile')
            .action(async () => {
                await this.configureProfile();
            });

        configCommand
            .command('delete-profile <name>')
            .description('Delete a profile')
            .action(async (name) => {
                await this.deleteProfile(name);
            });

        configCommand
            .command('update-profile <name>')
            .description('Update an existing profile')
            .action(async (name) => {
                if (!this.config.profiles[name]) {
                    console.error(`Profile ${name} does not exist.`);
                    return;
                }
                await this.configureProfile(name);
            });

    }

    async load() {
        try {
            const data = await readFile(CONFIG_PATH, 'utf-8');
            this.config = JSON.parse(data);
        } catch (err) {
            // Si le fichier n'existe pas, on garde la config par défaut
        }
        // Chargement du fichier de fallback secrets (si keyring indisponible)
        try {
            const enc = await readFile(SECRETS_PATH, 'utf-8');
            this._secretsCache = _decryptData(enc);
        } catch {
            this._secretsCache = {};
        }
        // Réappliquer le profil temporaire si défini (survit aux rechargements de config)
        if (this._temporaryProfile) {
            this.config.currentProfile = this._temporaryProfile;
        }
    }

    async save() {
        this.logger.debug('ConfigManager.save');
        await writeFile(CONFIG_PATH, JSON.stringify(this.config, null, 2));
    }

    // Dans ConfigManager.js
    useTemporaryProfile(name) {
        if (!this.config.profiles[name]) {
            const available = Object.keys(this.config.profiles).join(', ');
            throw new Error(`Profile "${name}" does not exist. Available: ${available}`);
        }
        this._temporaryProfile = name;   // mémorisé pour survivre aux rechargements
        this.config.currentProfile = name;
        this.logger.debug(`ConfigManager.useTemporaryProfile: using "${name}" for this invocation`);
    }

    async isConfigured() {
        this.logger.debug('ConfigManager.isConfigured');

        await this.load(); // On s'assure que la config est chargée
        const profile = this.config.currentProfile;
        const profileData = this.config.profiles[profile];

        this.logger.debug(`Current profile: ${profile}`);
        this.logger.debug(`Profile data: ${JSON.stringify(profileData)}`);

        if (!profileData || !profileData.host) return false;

        switch (profileData.authMethod) {
            case 'RAINBOW':
                return !!profileData.loginEmail;
            case 'API_KEY':
            case 'OAUTH':
                return !!profileData.applicationId;
            default:
                return false;
        }
    }


    getSecret(profileName, key) {
        this.logger.debug('ConfigManager.getSecret');
        const cacheKey = `${profileName}::${key}`;
        try {
            const keyringEntry = new Entry(`${SERVICE_NAME}_${profileName}`, key);
            const value = keyringEntry.getPassword();
            if (value != null) return value;
        } catch (error) {
            this.logger.debug(`Keyring indisponible pour ${profileName}/${key}: ${error.message}`);
            if (!this._keyringWarnShown) {
                this._keyringWarnShown = true;
                console.warn('  [info] Keyring OS indisponible — utilisation du fichier de fallback chiffré (~/.rainbow-cli-secrets.enc)');
            }
        }
        // Fallback : fichier chiffré chargé en mémoire au démarrage
        return this._secretsCache[cacheKey] ?? null;
    }

    async setSecret(profileName, key, value) {
        this.logger.debug('ConfigManager.setSecret');
        if (!value) return;
        const cacheKey = `${profileName}::${key}`;
        // Mise à jour du cache et sauvegarde du fichier de fallback
        this._secretsCache[cacheKey] = value;
        await writeFile(SECRETS_PATH, _encryptData(this._secretsCache), { mode: 0o600 });
        // Tentative d'écriture dans le keyring OS (best-effort)
        try {
            const keyringEntry = new Entry(`${SERVICE_NAME}_${profileName}`, key);
            keyringEntry.setPassword(value);
        } catch (error) {
            this.logger.debug(`Keyring write failed pour ${profileName}/${key}: ${error.message}`);
        }
    }

    deleteSecret(profileName, key) {
        this.logger.debug('ConfigManager.deleteSecret');
        try {
            this.keyringEntry.deletePassword(`${profileName}_${key}`);
        }
        catch (error) {
            //this.logger.error(`Error deleting secret for ${profileName} and key ${key}: ${error}`);
        }
    }

    async getFullCredentials() {
        this.logger.debug('ConfigManager.getFullCredentials');
        const profileName = this.config.currentProfile;
        const profileData = this.config.profiles[profileName];

        if (!profileData) return null;

        switch (profileData.authMethod) {
            case 'API_KEY':
                return await this.getApiKeyCredentials(profileName, profileData);
            case 'RAINBOW':
                return await this.getRainbowCredentials(profileName, profileData);
            case 'OAUTH':
                return await this.getOAuthCredentials(profileName, profileData);
            default:
                throw new Error(`Unknown authentication method: ${profileData.authMethod}`);
        }
    }

    async getApiKeyCredentials(profileName, profileData) {
        this.logger.debug('ConfigManager.getApiKeyCredentials');
        const apiKey = this.getSecret(profileName, 'apiKey');
        const token = this.getSecret(profileName, 'token');
        const applicationSecret = this.getSecret(profileName, 'applicationSecret');
        const proxyPassword = this.getSecret(profileName, 'proxyPassword');

        return {
            ...profileData,
            apiKey,
            token,
            applicationSecret,
            proxyPassword
        };
    }

    async getRainbowCredentials(profileName, profileData) {
        this.logger.debug('ConfigManager.getRainbowCredentials');
        const password = this.getSecret(profileName, 'password');
        const applicationSecret = this.getSecret(profileName, 'applicationSecret');
        const token = this.getSecret(profileName, 'token');
        const proxyPassword = this.getSecret(profileName, 'proxyPassword');

        return {
            ...profileData,
            password,
            applicationSecret,
            token,
            proxyPassword
        };
    }

    async getOAuthCredentials(profileName, profileData) {
        this.logger.debug('ConfigManager.getOAuthCredentials');
        const applicationSecret = this.getSecret(profileName, 'applicationSecret');
        const token = this.getSecret(profileName, 'token');
        const refreshToken = this.getSecret(profileName, 'refreshToken');
        const proxyPassword = this.getSecret(profileName, 'proxyPassword');

        return {
            ...profileData,
            applicationSecret,
            token,
            refreshToken,
            proxyPassword
        };
    }

    /**
     * Set profile data and secrets
     * @param {*} profileName: profile name
     * @param {*} profileData: { authMethod, username, applicationId }
     * @param {*} secrets: { apiKey, password, applicationSecret }
     */
    async setProfile(profileName, profileData, secrets) {
        this.logger.debug('ConfigManager.setProfile');
        // Sauvegarde des infos non-sensibles en clair
        this.config.profiles[profileName] = profileData;
        this.config.currentProfile = profileName;
        await this.save();

        switch (profileData.authMethod) {
            case 'API_KEY':
                if (secrets.apiKey) await this.setSecret(profileName, 'apiKey', secrets.apiKey);
                if (secrets.applicationSecret) await this.setSecret(profileName, 'applicationSecret', secrets.applicationSecret);
                break;
            case 'RAINBOW':
                if (secrets.password) await this.setSecret(profileName, 'password', secrets.password);
                if (secrets.applicationSecret) await this.setSecret(profileName, 'applicationSecret', secrets.applicationSecret);
                break;
            case 'OAUTH':
                if (secrets.applicationSecret) await this.setSecret(profileName, 'applicationSecret', secrets.applicationSecret);
                break;
        }
        if (secrets.proxyPassword) await this.setSecret(profileName, 'proxyPassword', secrets.proxyPassword);
    }

    // Si existingProfileName est fourni, on pré-remplit les valeurs avec celles du profil existant pour faciliter la mise à jour
    async configureProfile(existingProfileName) {
        this.logger.debug('ConfigManager.configureProfile');

        const { default: input } = await import('@inquirer/input'); const { default: select } = await import('@inquirer/select'); const { default: password } = await import('@inquirer/password');

        let profileData = {};

        if (existingProfileName) {
            profileData = { ...this.config.profiles[existingProfileName] };
        }

        // Si le paramètre existingProfileName n'est pas fourni, il s'agit d'une création de profil, on demande un nom de profil à l'utilisateur
        if (!existingProfileName) {
            profileData.name = await input({
                message: 'Enter profile name:',
                default: this.config.currentProfile || 'default'
            });
            if (this.config.profiles[profileData.name]) {
                console.error(`Profile ${profileData.name} already exists. Please choose a different name or delete the existing profile first.`);
                return;
            }
        } else {
            // Il s'agit d'une mise à jour de profil, on garde le même nom de profil et on pré-remplit les valeurs existantes
            profileData.name = existingProfileName;
        }

        const server = await select({
            message: 'Select server:',
            choices: [
                { name: 'Sandbox (Developer)', value: 'sandbox.openrainbow.com' },
                { name: 'Production', value: 'openrainbow.com' },
                { name: 'DotNet', value: 'openrainbow.net' },
                { name: 'Custom', value: 'custom' }
            ]
        });

        if (server === 'custom') {
            profileData.host = await input({
                message: 'Enter custom server URL:'
            });
        } else {
            profileData.host = server;
        }

        // Credentials pré-configurés — présents uniquement dans les builds packagés, valides uniquement pour Production
        let _appDefaults = null;
        if (server === 'openrainbow.com') {
            try {
                const { getDefaultAppCredentials } = await import('./appdefaults.js');
                _appDefaults = getDefaultAppCredentials();
            } catch { /* appdefaults absent dans ce build */ }
        }
        if (_appDefaults) {
            console.log('  ℹ  Application credentials pre-configured — press Enter to use them.');
        }

        profileData.applicationId = await input({
            message: 'Enter Application ID:',
            default: profileData.applicationId || _appDefaults?.applicationId || ''
        });

        // @inquirer/password ignore l'option `default` avec mask actif → fallback manuel après le prompt
        // Priorité : valeur saisie > secret existant (update) > credentials pré-configurés (build) > vide
        const _existingSecret = existingProfileName ? this.getSecret(existingProfileName, 'applicationSecret') : null;
        const _secretInput = await password({
            message: (_existingSecret || _appDefaults?.applicationSecret)
                ? 'Enter Application Secret (Enter to keep/use pre-configured):'
                : 'Enter Application Secret:',
            mask: '*'
        });
        profileData.applicationSecret = _secretInput || _existingSecret || _appDefaults?.applicationSecret || '';

        profileData.authMethod = await select({
            message: 'Select authentication method:',
            choices: [
                { name: 'API Key', value: 'API_KEY' },
                { name: 'Rainbow Credentials', value: 'RAINBOW' },
                { name: 'OAuth (Browser)', value: 'OAUTH' }
            ],
            default: profileData.authMethod || 'API_KEY'
        });

        if (profileData.authMethod === 'API_KEY') {
            profileData.apiKey = await input({
                message: 'Enter API Key:',
                default: profileData.apiKey || ''
            });
        }
        if (profileData.authMethod === 'RAINBOW') {
            profileData.loginEmail = await input({
                message: 'Enter Username (email):',
                default: profileData.loginEmail || ''
            });
            profileData.userPassword = await password({
                message: 'Enter Password:',
                default: profileData.userPassword || '',
                mask: '*'
            });
        }

        // --- Configuration du proxy (optionnelle) ---
        const useProxy = await select({
            message: 'Configure a proxy?',
            choices: [
                { name: 'No', value: false },
                { name: 'Yes', value: true }
            ],
            default: !!profileData.proxy
        });

        if (useProxy) {
            profileData.proxy = profileData.proxy || {};
            profileData.proxy.host = await input({
                message: 'Proxy host:',
                default: profileData.proxy.host || ''
            });
            const proxyPortStr = await input({
                message: 'Proxy port:',
                default: String(profileData.proxy.port || 8080),
                validate: (v) => /^\d+$/.test(v) || 'Port must be a number'
            });
            profileData.proxy.port = parseInt(proxyPortStr, 10);

            const useProxyAuth = await select({
                message: 'Proxy requires authentication?',
                choices: [
                    { name: 'No', value: false },
                    { name: 'Yes', value: true }
                ],
                default: !!profileData.proxy.username
            });

            if (useProxyAuth) {
                profileData.proxy.username = await input({
                    message: 'Proxy username:',
                    default: profileData.proxy.username || ''
                });
                profileData.proxyPassword = await password({
                    message: 'Proxy password:',
                    mask: '*'
                });
            } else {
                delete profileData.proxy.username;
                profileData.proxyPassword = null;
            }
        } else {
            delete profileData.proxy;
            profileData.proxyPassword = null;
        }

        // Séparation des données et des secrets
        const { apiKey, userPassword, applicationSecret, proxyPassword, ...data } = profileData;
        const secrets = { apiKey, password: userPassword, applicationSecret, proxyPassword };

        await this.setProfile(profileData.name, data, secrets);

        console.log(`Profile ${profileData.name} configured successfully.`);
    }

    async deleteProfile(name) {
        if (!this.config.profiles[name]) {
            console.error(`Profile ${name} does not exist.`);
            return;
        }

        // Confirmer que l'utilisateur veut vraiment supprimer le profil
        const { default: select } = await import('@inquirer/select');
        const confirm = await select({
            message: `Are you sure you want to delete profile "${name}"? This action cannot be undone.`,
            choices: [
                { name: 'Yes, delete it', value: true },
                { name: 'No, keep it', value: false }
            ]
        });

        if (!confirm) {
            console.log('Profile deletion cancelled.');
            return;
        }

        delete this.config.profiles[name];
        if (this.config.currentProfile === name) {
            this.config.currentProfile = Object.keys(this.config.profiles)[0] || 'default';
        }
        await this.save();
        console.log(`Profile ${name} deleted.`);
    }

    async setToken(token) {
        this.logger.debug('ConfigManager.setToken');
        await this.setSecret(this.config.currentProfile, 'token', token);
    }

    getToken() {
        this.logger.debug('ConfigManager.getToken');
        return this.getSecret(this.config.currentProfile, 'token');
    }

    async setRefreshToken(refreshToken) {
        this.logger.debug('ConfigManager.setRefreshToken');
        await this.setSecret(this.config.currentProfile, 'refreshToken', refreshToken);
    }

    getRefreshToken() {
        this.logger.debug('ConfigManager.getRefreshToken');
        return this.getSecret(this.config.currentProfile, 'refreshToken');
    }

    /**
     * Set company ID for the current profile once the user is logged in
     * @param {string} companyId 
     */
    setCompanyId(companyId) {
        this.logger.debug('ConfigManager.setCompanyId');
        this.profiles[this.config.currentProfile].companyId = companyId;
        this.save();
    }

    /**
     *
     * @returns companyId of the current profile logged in user
     */
    getCompanyId() {
        this.logger.debug('ConfigManager.getCompanyId');
        return this.profiles[this.config.currentProfile].companyId;
    }

}
