import fs from 'fs';
import path from 'path';
import crypto from 'crypto';

// ============================================================
//  User Store — Fichier JSON avec hashing scrypt
// ============================================================

const USERS_DIR = path.join(process.cwd(), 'database');
const USERS_FILE = path.join(USERS_DIR, 'users.json');

// Scrypt parameters (NIST SP 800-132 / OWASP recommendations)
const SCRYPT_KEYLEN = 64;
const SCRYPT_SALT_LEN = 32;
const SCRYPT_OPTIONS: crypto.ScryptOptions = {
    N: 16384,   // CPU/memory cost (2^14)
    r: 8,       // Block size
    p: 1,       // Parallelism
};

// Input validation constraints
const USERNAME_MIN = 3;
const USERNAME_MAX = 32;
const USERNAME_REGEX = /^[a-zA-Z0-9_.-]+$/;
const PASSWORD_MIN = 8;
const PASSWORD_MAX = 128;

// Password generation charset
const PASSWORD_CHARS = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!@#$%&*_+-=';

export interface User {
    id: string;
    username: string;
    passwordHash: string;          // format: "salt_hex:hash_hex"
    role: 'admin' | 'user';
    createdAt: string;             // ISO 8601
    passwordChangedAt: string;     // ISO 8601 — pour invalidation session post-reset
    mustChangePassword?: boolean;
    preferences?: Record<string, any>;
}

// Données publiques (jamais le hash)
export interface PublicUser {
    id: string;
    username: string;
    role: 'admin' | 'user';
    createdAt: string;
    mustChangePassword?: boolean;
    preferences?: Record<string, any>;
}

// ========================= INTERNAL HELPERS =========================

function loadUsers(): User[] {
    try {
        if (!fs.existsSync(USERS_FILE)) return [];
        const data = fs.readFileSync(USERS_FILE, 'utf8');
        return JSON.parse(data);
    } catch (error: any) {
        console.error('[UserStore] Erreur lecture users.json:', error.message);
        return [];
    }
}

function saveUsers(users: User[]): void {
    try {
        if (!fs.existsSync(USERS_DIR)) {
            fs.mkdirSync(USERS_DIR, { recursive: true });
        }
        fs.writeFileSync(USERS_FILE, JSON.stringify(users, null, 2), 'utf8');
    } catch (error: any) {
        console.error('[UserStore] Erreur écriture users.json:', error.message);
        throw new Error('Impossible de sauvegarder les utilisateurs.');
    }
}

function hashPassword(password: string, salt: Buffer): string {
    const hash = crypto.scryptSync(password, salt, SCRYPT_KEYLEN, SCRYPT_OPTIONS);
    return `${salt.toString('hex')}:${hash.toString('hex')}`;
}

function verifyPasswordHash(password: string, stored: string): boolean {
    const [saltHex, hashHex] = stored.split(':');
    if (!saltHex || !hashHex) return false;

    const salt = Buffer.from(saltHex, 'hex');
    const storedHash = Buffer.from(hashHex, 'hex');
    const computedHash = crypto.scryptSync(password, salt, SCRYPT_KEYLEN, SCRYPT_OPTIONS);

    // Vérification en temps constant — anti timing attack
    if (storedHash.length !== computedHash.length) return false;
    return crypto.timingSafeEqual(storedHash, computedHash);
}

/** Génère un mot de passe aléatoire cryptographiquement sûr et conforme aux exigences */
function generatePassword(length: number = 16): string {
    while (true) {
        const bytes = crypto.randomBytes(length);
        let password = '';
        for (let i = 0; i < length; i++) {
            password += PASSWORD_CHARS[bytes[i] % PASSWORD_CHARS.length];
        }
        if (validatePassword(password) === null) {
            return password;
        }
    }
}

/** Exécute un scrypt bidon pour uniformiser le temps de réponse (anti user-enumeration) */
export function dummyScrypt(): void {
    const fakeSalt = crypto.randomBytes(SCRYPT_SALT_LEN);
    crypto.scryptSync('dummy-password-for-timing', fakeSalt, SCRYPT_KEYLEN, SCRYPT_OPTIONS);
}

// ========================= VALIDATION =========================

function validateUsername(username: string): string | null {
    if (!username || typeof username !== 'string') return 'Nom d\'utilisateur requis.';
    const trimmed = username.trim();
    if (trimmed.length < USERNAME_MIN) return `Nom d'utilisateur trop court (min ${USERNAME_MIN} caractères).`;
    if (trimmed.length > USERNAME_MAX) return `Nom d'utilisateur trop long (max ${USERNAME_MAX} caractères).`;
    if (!USERNAME_REGEX.test(trimmed)) return 'Nom d\'utilisateur invalide (lettres, chiffres, _ . - uniquement).';
    return null;
}

export function validatePassword(password: string): string | null {
    if (!password || typeof password !== 'string') return 'Mot de passe requis.';
    if (password.length < PASSWORD_MIN) return `Le mot de passe doit faire au moins ${PASSWORD_MIN} caractères.`;
    if (password.length > PASSWORD_MAX) return `Le mot de passe est trop long (max ${PASSWORD_MAX} caractères).`;
    if (!/[A-Z]/.test(password)) return "Le mot de passe doit contenir au moins une lettre majuscule.";
    if (!/[0-9]/.test(password)) return "Le mot de passe doit contenir au moins un chiffre.";
    if (!/[^a-zA-Z0-9]/.test(password)) return "Le mot de passe doit contenir au moins un caractère spécial (ex: !, @, #, $, %...).";
    return null;
}

// ========================= PUBLIC API =========================

/** Vérifie si au moins un utilisateur existe */
export function hasAnyUser(): boolean {
    return loadUsers().length > 0;
}

/** Retourne tous les utilisateurs (sans les hash de mots de passe) */
export function getAllUsers(): PublicUser[] {
    return loadUsers().map(({ id, username, role, createdAt, mustChangePassword, preferences }) => ({
        id, username, role, createdAt, mustChangePassword, preferences
    }));
}

/** Retourne un user par ID (données internes, avec hash) */
export function getUserById(id: string): User | null {
    return loadUsers().find(u => u.id === id) || null;
}

/** Retourne un user par username (case-insensitive) */
export function getUserByUsername(username: string): User | null {
    const lower = username.toLowerCase().trim();
    return loadUsers().find(u => u.username.toLowerCase() === lower) || null;
}

/** Crée un nouvel utilisateur */
export function createUser(username: string, password: string, role: 'admin' | 'user', mustChangePassword = false): { user: PublicUser; clearPassword: string } {
    // Validation
    const usernameError = validateUsername(username);
    if (usernameError) throw new Error(usernameError);

    const passwordError = validatePassword(password);
    if (passwordError) throw new Error(passwordError);

    const users = loadUsers();

    // Unicité du username (case-insensitive)
    if (users.some(u => u.username.toLowerCase() === username.toLowerCase().trim())) {
        throw new Error('Ce nom d\'utilisateur est déjà pris.');
    }

    const salt = crypto.randomBytes(SCRYPT_SALT_LEN);
    const now = new Date().toISOString();

    const newUser: User = {
        id: crypto.randomUUID(),
        username: username.trim(),
        passwordHash: hashPassword(password, salt),
        role,
        createdAt: now,
        passwordChangedAt: now,
        mustChangePassword,
        preferences: {},
    };

    users.push(newUser);
    saveUsers(users);

    console.log(`[UserStore] Utilisateur créé: ${newUser.username} (${newUser.role}), mustChangePassword: ${mustChangePassword}`);

    return {
        user: { id: newUser.id, username: newUser.username, role: newUser.role, createdAt: newUser.createdAt, mustChangePassword: newUser.mustChangePassword, preferences: newUser.preferences },
        clearPassword: password,
    };
}

/** Crée un utilisateur avec un mot de passe auto-généré */
export function createUserWithGeneratedPassword(username: string, role: 'admin' | 'user'): { user: PublicUser; clearPassword: string } {
    const password = generatePassword(16);
    return createUser(username, password, role, true);
}

/** Vérifie les identifiants et retourne le user (ou null) */
export function verifyCredentials(username: string, password: string): User | null {
    const user = getUserByUsername(username);

    if (!user) {
        // Anti user-enumeration : on fait un scrypt bidon pour uniformiser le temps de réponse
        dummyScrypt();
        return null;
    }

    if (verifyPasswordHash(password, user.passwordHash)) {
        return user;
    }

    return null;
}

/** Supprime un utilisateur par ID */
export function deleteUser(id: string): boolean {
    const users = loadUsers();
    const userToDelete = users.find(u => u.id === id);

    if (!userToDelete) throw new Error('Utilisateur introuvable.');

    // Anti-lockout : interdire la suppression du dernier admin
    if (userToDelete.role === 'admin') {
        const adminCount = users.filter(u => u.role === 'admin').length;
        if (adminCount <= 1) {
            throw new Error('Impossible de supprimer le dernier administrateur.');
        }
    }

    const filtered = users.filter(u => u.id !== id);
    saveUsers(filtered);

    console.log(`[UserStore] Utilisateur supprimé: ${userToDelete.username}`);
    return true;
}

/** Reset le mot de passe d'un utilisateur et active mustChangePassword */
export function resetPassword(id: string): { clearPassword: string } {
    const users = loadUsers();
    const user = users.find(u => u.id === id);

    if (!user) throw new Error('Utilisateur introuvable.');

    const newPassword = generatePassword(16);
    const salt = crypto.randomBytes(SCRYPT_SALT_LEN);

    user.passwordHash = hashPassword(newPassword, salt);
    user.passwordChangedAt = new Date().toISOString();
    user.mustChangePassword = true;

    saveUsers(users);

    console.log(`[UserStore] Mot de passe réinitialisé pour: ${user.username}`);
    return { clearPassword: newPassword };
}

/** Change le mot de passe d'un utilisateur après vérification de la complexité */
export function changeUserPassword(id: string, newPassword: string): void {
    const passwordError = validatePassword(newPassword);
    if (passwordError) throw new Error(passwordError);

    const users = loadUsers();
    const user = users.find(u => u.id === id);

    if (!user) throw new Error('Utilisateur introuvable.');

    const salt = crypto.randomBytes(SCRYPT_SALT_LEN);
    user.passwordHash = hashPassword(newPassword, salt);
    user.passwordChangedAt = new Date().toISOString();
    user.mustChangePassword = false;

    saveUsers(users);
    console.log(`[UserStore] Mot de passe modifié avec succès pour: ${user.username}`);
}

/** Met à jour les préférences d'un utilisateur */
export function updateUserPreferences(id: string, updates: Record<string, any>): void {
    const users = loadUsers();
    const user = users.find(u => u.id === id);

    if (!user) throw new Error('Utilisateur introuvable.');

    user.preferences = { ...user.preferences, ...updates };

    saveUsers(users);
    console.log(`[UserStore] Préférences mises à jour pour: ${user.username}`);
}
