const bcrypt = require('bcryptjs');
const db = require('../config/db');
const { logAuditFromReq } = require('../middlewares/audit');
const otplib = require('otplib');

exports.getAll = async (_req, res, next) => {
  try {
    const [rows] = await db.query(
      `SELECT id, username, full_name, role, permissions, avatar_url, is_active,
              totp_enabled, totp_verified,
              created_at, updated_at
       FROM users ORDER BY id`
    );
    // Parse permissions JSON
    for (const row of rows) {
      try { row.permissions = typeof row.permissions === 'string' ? JSON.parse(row.permissions) : row.permissions; } catch(_) {}
    }
    res.json(rows);
  } catch (err) {
    next(err);
  }
};

exports.getById = async (req, res, next) => {
  try {
    const [rows] = await db.query(
      'SELECT id, username, full_name, role, permissions, avatar_url, is_active, created_at, updated_at FROM users WHERE id = ?',
      [req.params.id]
    );
    if (rows.length === 0) return res.status(404).json({ error: 'User not found' });
    const u = rows[0];
    try { u.permissions = typeof u.permissions === 'string' ? JSON.parse(u.permissions) : u.permissions; } catch(_) {}
    res.json(u);
  } catch (err) {
    next(err);
  }
};

exports.update = async (req, res, next) => {
  try {
    const { full_name, role, is_active, permissions } = req.body;
    let avatar_url = undefined;
    if (req.file) {
      avatar_url = '/api/uploads/avatars/' + req.file.filename;
    }
    // Serialize permissions if provided
    const permJson = permissions !== undefined ? JSON.stringify(permissions) : undefined;
    const [result] = await db.query(
      `UPDATE users
       SET full_name    = COALESCE(?, full_name),
           role         = COALESCE(?, role),
           is_active    = COALESCE(?, is_active),
           permissions  = COALESCE(?, permissions),
           avatar_url   = COALESCE(?, avatar_url)
       WHERE id = ?`,
      [full_name, role, is_active, permJson, avatar_url || null, req.params.id]
    );
    if (result.affectedRows === 0) return res.status(404).json({ error: 'User not found' });

    const [rows] = await db.query(
      'SELECT id, username, full_name, role, permissions, avatar_url, is_active, updated_at FROM users WHERE id = ?',
      [req.params.id]
    );

    logAuditFromReq(req, 'update', 'user', parseInt(req.params.id, 10), {
      fields_updated: Object.keys(req.body),
    });

    res.json(rows[0]);
  } catch (err) {
    next(err);
  }
};

exports.resetPassword = async (req, res, next) => {
  try {
    const { password } = req.body;
    if (!password) return res.status(400).json({ error: 'password is required' });

    const salt = await bcrypt.genSalt(10);
    const password_hash = await bcrypt.hash(password, salt);

    const [result] = await db.query(
      'UPDATE users SET password_hash = ? WHERE id = ?',
      [password_hash, req.params.id]
    );
    if (result.affectedRows === 0) return res.status(404).json({ error: 'User not found' });

    logAuditFromReq(req, 'password_reset', 'user', parseInt(req.params.id, 10));

    res.json({ message: 'Password updated' });
  } catch (err) {
    next(err);
  }
};

exports.updatePin = async (req, res, next) => {
  try {
    const { pin } = req.body;
    if (!pin) return res.status(400).json({ error: 'pin is required' });

    const salt = await bcrypt.genSalt(10);
    const pin_hash = await bcrypt.hash(pin, salt);

    const [result] = await db.query(
      'UPDATE users SET pin_hash = ? WHERE id = ?',
      [pin_hash, req.params.id]
    );
    if (result.affectedRows === 0) return res.status(404).json({ error: 'User not found' });

    logAuditFromReq(req, 'pin_set', 'user', parseInt(req.params.id, 10));

    res.json({ message: 'PIN updated' });
  } catch (err) {
    next(err);
  }
};

// ── Self-update PIN (any authenticated user) ──
exports.updateOwnPin = async (req, res, next) => {
  try {
    const userId = req.user.id;
    const { pin } = req.body;
    if (!pin) return res.status(400).json({ error: 'pin is required' });

    const salt = await bcrypt.genSalt(10);
    const pin_hash = await bcrypt.hash(pin, salt);

    const [result] = await db.query(
      'UPDATE users SET pin_hash = ? WHERE id = ?',
      [pin_hash, userId]
    );
    if (result.affectedRows === 0) return res.status(404).json({ error: 'User not found' });

    logAuditFromReq(req, 'pin_change', 'user', userId);

    res.json({ message: 'PIN updated' });
  } catch (err) {
    next(err);
  }
};

exports.remove = async (req, res, next) => {
  try {
    const [result] = await db.query(
      'UPDATE users SET is_active = 0 WHERE id = ?',
      [req.params.id]
    );
    if (result.affectedRows === 0) return res.status(404).json({ error: 'User not found' });

    logAuditFromReq(req, 'deactivate', 'user', parseInt(req.params.id, 10));

    res.json({ message: 'User deactivated' });
  } catch (err) {
    next(err);
  }
};

// ── Self-update profile (any authenticated user) ──
exports.updateProfile = async (req, res, next) => {
  try {
    const userId = req.user.id;
    const { full_name } = req.body;

    let avatar_url = undefined;
    if (req.file) {
      avatar_url = '/api/uploads/avatars/' + req.file.filename;
    }

    const [result] = await db.query(
      `UPDATE users
       SET full_name  = COALESCE(?, full_name),
           avatar_url = COALESCE(?, avatar_url)
       WHERE id = ?`,
      [full_name, avatar_url || null, userId]
    );
    if (result.affectedRows === 0) return res.status(404).json({ error: 'User not found' });

    const [rows] = await db.query(
      'SELECT id, username, full_name, role, avatar_url, is_active, updated_at FROM users WHERE id = ?',
      [userId]
    );

    logAuditFromReq(req, 'profile_update', 'user', userId, {
      fields_updated: Object.keys(req.body).concat(req.file ? ['avatar'] : []),
    });

    res.json(rows[0]);
  } catch (err) {
    next(err);
  }
};

// ── Self-change password ──
exports.changeOwnPassword = async (req, res, next) => {
  try {
    const userId = req.user.id;
    const { current_password, new_password } = req.body;
    if (!current_password || !new_password) {
      return res.status(400).json({ error: 'current_password and new_password are required' });
    }

    const [users] = await db.query('SELECT * FROM users WHERE id = ?', [userId]);
    if (users.length === 0) return res.status(404).json({ error: 'User not found' });

    const valid = await bcrypt.compare(current_password, users[0].password_hash);
    if (!valid) return res.status(401).json({ error: 'Current password is incorrect' });

    const salt = await bcrypt.genSalt(10);
    const password_hash = await bcrypt.hash(new_password, salt);
    await db.query('UPDATE users SET password_hash = ? WHERE id = ?', [password_hash, userId]);

    logAuditFromReq(req, 'password_change', 'user', userId);

    res.json({ message: 'Password changed successfully' });
  } catch (err) {
    next(err);
  }
};

// ── Enable MFA for a user (admin/manager) ──
// Generates a TOTP secret, stores it, returns the secret + otpauth URL
exports.enableMfa = async (req, res, next) => {
  try {
    const userId = parseInt(req.params.id, 10);

    const [users] = await db.query('SELECT * FROM users WHERE id = ?', [userId]);
    if (users.length === 0) return res.status(404).json({ error: 'User not found' });

    const user = users[0];

    // Generate a new TOTP secret
    const secret = otplib.generateSecret();
    const otpauthUrl = otplib.generateURI({ secret, issuer: 'NexusOS', accountName: user.username });

    // Save secret to DB but mark as not yet verified
    await db.query(
      'UPDATE users SET totp_secret = ?, totp_enabled = 1, totp_verified = 0 WHERE id = ?',
      [secret, userId]
    );

    logAuditFromReq(req, 'mfa_enable_initiated', 'user', userId);

    res.json({
      secret,
      otpauth_url: otpauthUrl,
      message: 'Scan the QR code with your authenticator app, then verify with a code.',
    });
  } catch (err) {
    next(err);
  }
};

// ── Verify MFA setup (admin/manager confirms the user's first TOTP code) ──
exports.verifyMfaSetup = async (req, res, next) => {
  try {
    const userId = parseInt(req.params.id, 10);
    const { totp_code } = req.body;

    if (!totp_code) return res.status(400).json({ error: 'totp_code is required' });

    const [users] = await db.query('SELECT * FROM users WHERE id = ?', [userId]);
    if (users.length === 0) return res.status(404).json({ error: 'User not found' });

    const user = users[0];
    if (!user.totp_secret) {
      return res.status(400).json({ error: 'MFA not initiated for this user. Enable MFA first.' });
    }

    const totpResult = otplib.verifySync({ token: totp_code, secret: user.totp_secret });
    if (!totpResult.valid) {
      return res.status(400).json({ error: 'Invalid code. Make sure your authenticator app is synced correctly.' });
    }

    await db.query(
      'UPDATE users SET totp_verified = 1 WHERE id = ?',
      [userId]
    );

    logAuditFromReq(req, 'mfa_verified', 'user', userId);

    res.json({ message: 'MFA setup verified successfully. MFA is now active.' });
  } catch (err) {
    next(err);
  }
};

// ── Disable MFA for a user (admin/manager) ──
exports.disableMfa = async (req, res, next) => {
  try {
    const userId = parseInt(req.params.id, 10);

    const [result] = await db.query(
      'UPDATE users SET totp_secret = NULL, totp_enabled = 0, totp_verified = 0 WHERE id = ?',
      [userId]
    );
    if (result.affectedRows === 0) return res.status(404).json({ error: 'User not found' });

    logAuditFromReq(req, 'mfa_disabled', 'user', userId);

    res.json({ message: 'MFA disabled successfully' });
  } catch (err) {
    next(err);
  }
};
