const crypto = require('crypto');
const MailService = require('../../services/email/MailService');
const UnitOfWork = require('../../database/UnitOfWork');
const PersonalUserRepository = require('../../repositories/user/personal/PersonalUserRepository');
const CompanyUserRepository = require('../../repositories/user/company/CompanyUserRepository');
const PasswordResetRepository = require('../../repositories/passwordReset/PasswordResetRepository');
const User = require('../../models/User');
const { logger } = require('../../middleware/logger');

function generateToken() {
  return crypto.randomBytes(32).toString('hex');
}

module.exports = {
  async requestPasswordReset(req, res) {
    // If rate limiter already sent a response, do not continue
    if (res.headersSent) return;

    const { email } = req.body;
    if (!email || typeof email !== 'string') {
      logger.warn('passwordReset:invalid_request', { email });
      return res.status(400).json({ success: false, message: 'Invalid request.' });
    }

    // Always respond generically
    let user = null, userType = null;
    const uow = new UnitOfWork();
    try {
      await uow.start();
      const personalRepo = new PersonalUserRepository(uow);
      const companyRepo = new CompanyUserRepository(uow);
      const passwordResetRepo = new PasswordResetRepository(uow);

      user = await personalRepo.findByEmail(email);
      userType = user ? 'personal' : null;
      if (!user) {
        user = await companyRepo.findByEmail(email);
        userType = user ? 'company' : null;
      }

      if (user) {
        logger.info('passwordReset:user_found', { userId: user.id, userType });
        const existingToken = await passwordResetRepo.findValidTokenByUserId(user.id);
        if (existingToken) {
          await uow.rollback();
          logger.info('passwordReset:existing_token', { userId: user.id });
          return res.json({
            success: true,
            message: 'A password reset link has already been sent and is still valid. Please check your email.'
          });
        }

        // Generate token and expiry
        const token = generateToken();
        const expiresAt = new Date(Date.now() + 25 * 60 * 1000);

        // Store in password_resets
        await passwordResetRepo.createToken(user.id, token, expiresAt);
        logger.info('passwordReset:token_created', { userId: user.id, token });

        // Send email
        await MailService.sendPasswordResetEmail({
          email,
          firstName: userType === 'personal' ? user.firstName : undefined,
          companyName: userType === 'company' ? user.companyName : undefined,
          token,
          lang: req.body.lang || 'en'
        });
        logger.info('passwordReset:email_sent', { email });
      }

      await uow.commit();
    } catch (err) {
      await uow.rollback();
      logger.error('passwordReset:request_error', { message: err?.message, stack: err?.stack });
    }
    return res.json({
      success: true,
      message: 'If an account exists for this email, a password reset link has been sent.'
    });
  },

  async verifyPasswordResetToken(req, res) {
    const { token } = req.query;
    if (!token || typeof token !== 'string') {
      logger.warn('passwordReset:invalid_token', { token });
      return res.status(400).json({ success: false, message: 'Invalid token.' });
    }
    const uow = new UnitOfWork();
    try {
      await uow.start();
      const passwordResetRepo = new PasswordResetRepository(uow);
      const row = await passwordResetRepo.findValidToken(token);
      if (!row) {
        await uow.rollback();
        logger.warn('passwordReset:token_invalid_or_expired', { token });
        return res.status(400).json({ success: false, message: 'Invalid or expired token.' });
      }
      await uow.commit();
      logger.info('passwordReset:token_valid', { token });
      return res.json({ success: true, message: 'Token is valid.' });
    } catch (err) {
      await uow.rollback();
      logger.error('passwordReset:verify_error', { error: err });
      return res.status(500).json({ success: false, message: 'Internal server error.' });
    }
  },

  async resetPassword(req, res) {
    const { token, newPassword } = req.body;
    if (!token || typeof token !== 'string' || !newPassword || typeof newPassword !== 'string' || newPassword.length < 8) {
      logger.warn('passwordReset:invalid_reset_request', { token });
      return res.status(400).json({ success: false, message: 'Invalid request.' });
    }
    const uow = new UnitOfWork();
    try {
      await uow.start();
      const passwordResetRepo = new PasswordResetRepository(uow);
      const row = await passwordResetRepo.findValidToken(token);
      if (!row) {
        await uow.rollback();
        logger.warn('passwordReset:token_invalid_or_expired', { token });
        return res.status(400).json({ success: false, message: 'Invalid or expired token.' });
      }
      // Update password
      const hashed = await User.hashPassword(newPassword);
      await passwordResetRepo.updateUserPassword(row.user_id, hashed);
      await passwordResetRepo.markTokenUsed(row.id);
      await uow.commit();
      logger.info('passwordReset:password_reset', { userId: row.user_id });
      return res.json({ success: true, message: 'Password has been reset.' });
    } catch (err) {
      await uow.rollback();
      logger.error('passwordReset:reset_error', { error: err });
      return res.status(500).json({ success: false, message: 'Internal server error.' });
    }
  }
};