const UserRepository = require('../../repositories/user/UserRepository');
const LoginRepository = require('../../repositories/login/LoginRepository');
const UserStatusRepository = require('../../repositories/status/UserStatusRepository');
const UnitOfWork = require('../../database/UnitOfWork');
const jwt = require('jsonwebtoken');
const crypto = require('crypto');
const { logger } = require('../../middleware/logger');

class LoginService {
  static async login(email, password) {
    logger.info('LoginService.login:start', { email });
    const unitOfWork = new UnitOfWork();
    await unitOfWork.start();
    unitOfWork.registerRepository('user', new UserRepository(unitOfWork));
    unitOfWork.registerRepository('login', new LoginRepository(unitOfWork));
    unitOfWork.registerRepository('status', new UserStatusRepository(unitOfWork));

    try {
      // Find user by email
      const userRepo = unitOfWork.getRepository('user');
      const loginRepo = unitOfWork.getRepository('login');
      const user = await userRepo.findUserByEmail(email);
      if (!user) {
        logger.warn('LoginService.login:user_not_found', { email });
        await unitOfWork.rollback();
        const error = new Error('Invalid credentials');
        error.status = 401;
        throw error;
      }

      // Validate password
      const valid = await user.comparePassword(password);
      if (!valid) {
        logger.warn('LoginService.login:invalid_password', { email });
        await unitOfWork.rollback();
        const error = new Error('Invalid credentials');
        error.status = 401;
        throw error;
      }

      // Check user status (block suspended)
      const statusRepo = unitOfWork.getRepository('status');
      const userStatus = await statusRepo.getStatusByUserId(user.id);
      if (userStatus && userStatus.status === 'suspended') {
        logger.warn('LoginService.login:user_suspended', { userId: user.id, email: user.email });
        await unitOfWork.rollback();
        const error = new Error('Account suspended');
        error.status = 403;
        throw error;
      }

      // Generate access token (JWT)
      const accessToken = jwt.sign(
        { userId: user.id, email: user.email, userType: user.userType, role: user.role },
        process.env.JWT_SECRET,
        { expiresIn: process.env.JWT_EXPIRES_IN || '15m' }
      );
      logger.info('LoginService.login:access_token_issued', { userId: user.id, email: user.email });

      // Generate refresh token (random string)
      const refreshToken = crypto.randomBytes(64).toString('hex');
      // Use REFRESH_TOKEN_EXPIRES_IN from env, fallback to 7d
      const refreshExpiresMs = (() => {
        const envVal = process.env.REFRESH_TOKEN_EXPIRES_IN || '7d';
        if (envVal.endsWith('d')) return parseInt(envVal) * 24 * 60 * 60 * 1000;
        if (envVal.endsWith('h')) return parseInt(envVal) * 60 * 60 * 1000;
        if (envVal.endsWith('m')) return parseInt(envVal) * 60 * 1000;
        return 7 * 24 * 60 * 60 * 1000;
      })();
      const expiresAt = new Date(Date.now() + refreshExpiresMs);

      // Store refresh token in DB (atomic) via LoginRepository
      await loginRepo.insertRefreshToken(user.id, refreshToken, expiresAt);
      logger.info('LoginService.login:refresh_token_issued', { userId: user.id, email: user.email });

      // Update last_login_at in users table via LoginRepository
      await loginRepo.updateLastLogin(user.id);
      logger.info('LoginService.login:last_login_updated', { userId: user.id });

      // Fetch user permissions via LoginRepository
      const permissions = await loginRepo.getUserPermissions(user.id);
      logger.info('LoginService.login:permissions_fetched', { userId: user.id, count: permissions.length });

      await unitOfWork.commit();
      logger.info('LoginService.login:success', { userId: user.id });

      // Send access token in response
      return {
        accessToken,
        refreshToken,
        refreshTokenExpires: expiresAt,
        user: {
          ...user.getPublicData(),
          role: user.role,
          permissions // Add permissions array to response
        }
      };
    } catch (error) {
      logger.error('LoginService.login:error', { email, error: error.message });
      await unitOfWork.rollback(error);
      throw error;
    }
  }

  static async refresh(refreshToken) {
    logger.info('LoginService.refresh:start');
    const unitOfWork = new UnitOfWork();
    await unitOfWork.start();
    unitOfWork.registerRepository('login', new LoginRepository(unitOfWork));
    unitOfWork.registerRepository('user', new UserRepository(unitOfWork));
    unitOfWork.registerRepository('status', new UserStatusRepository(unitOfWork));
    try {
      const loginRepo = unitOfWork.getRepository('login');
      // Find refresh token in DB via LoginRepository
      const tokenRecord = await loginRepo.findRefreshToken(refreshToken);
      if (!tokenRecord) {
        logger.warn('LoginService.refresh:token_not_found');
        await unitOfWork.rollback();
        const error = new Error('Invalid or expired refresh token');
        error.status = 401;
        throw error;
      }
      const { user_id, expires_at } = tokenRecord;
      if (new Date(expires_at) < new Date()) {
        logger.warn('LoginService.refresh:token_expired');
        await unitOfWork.rollback();
        const error = new Error('Refresh token expired');
        error.status = 401;
        throw error;
      }

      // Rotate refresh token: revoke old, issue new via LoginRepository
      await loginRepo.revokeRefreshToken(refreshToken);

      const newRefreshToken = crypto.randomBytes(64).toString('hex');
      const refreshExpiresMs = (() => {
        const envVal = process.env.REFRESH_TOKEN_EXPIRES_IN || '7d';
        if (envVal.endsWith('d')) return parseInt(envVal) * 24 * 60 * 60 * 1000;
        if (envVal.endsWith('h')) return parseInt(envVal) * 60 * 60 * 1000;
        if (envVal.endsWith('m')) return parseInt(envVal) * 60 * 1000;
        return 7 * 24 * 60 * 60 * 1000;
      })();
      const newExpiresAt = new Date(Date.now() + refreshExpiresMs);

      await loginRepo.insertRefreshToken(user_id, newRefreshToken, newExpiresAt);
      logger.info('LoginService.refresh:refresh_token_rotated', { userId: user_id });

      // Get user via UserRepository
      const userRepo = unitOfWork.getRepository('user');
      const user = await userRepo.findUserByEmailOrId(user_id);

      // Check user status (block suspended)
      const statusRepo = unitOfWork.getRepository('status');
      const userStatus = await statusRepo.getStatusByUserId(user.id);
      if (userStatus && userStatus.status === 'suspended') {
        logger.warn('LoginService.refresh:user_suspended', { userId: user.id, email: user.email });
        await unitOfWork.rollback();
        const error = new Error('Account suspended');
        error.status = 403;
        throw error;
      }

      // Fetch user role directly from DB via LoginRepository
      const role = await loginRepo.getUserRole(user.id);

      // Fetch user permissions via LoginRepository
      const permissions = await loginRepo.getUserPermissions(user.id);
      logger.info('LoginService.refresh:user_fetched', { userId: user.id });
      logger.info('LoginService.refresh:role_fetched', { userId: user.id, role });
      logger.info('LoginService.refresh:permissions_fetched', { userId: user.id, count: permissions.length });

      // Generate new access token
      const accessToken = jwt.sign(
        { userId: user.id, email: user.email, userType: user.userType, role },
        process.env.JWT_SECRET,
        { expiresIn: process.env.JWT_EXPIRES_IN || '15m' }
      );
      logger.info('LoginService.refresh:access_token_issued', { userId: user.id, email: user.email });

      await unitOfWork.commit();
      logger.info('LoginService.refresh:success', { userId: user.id });

      return {
        accessToken,
        refreshToken: newRefreshToken,
        refreshTokenExpires: newExpiresAt,
        user: {
          ...user.getPublicData(),
          role,
          permissions
        }
      };
    } catch (error) {
      logger.error('LoginService.refresh:error', { error: error.message });
      // Improved error logging for refresh token rotation failures
      console.error('💥 Error during refresh token rotation:', error);
      if (error && error.stack) {
        console.error('💥 Error stack:', error.stack);
      }
      await unitOfWork.rollback(error);
      throw error;
    }
  }

  static async logout(refreshToken) {
    logger.info('LoginService.logout:start');
    const unitOfWork = new UnitOfWork();
    await unitOfWork.start();
    unitOfWork.registerRepository('login', new LoginRepository(unitOfWork));
    try {
      const loginRepo = unitOfWork.getRepository('login');
      // Mark refresh token as revoked via LoginRepository
      await loginRepo.revokeRefreshToken(refreshToken);
      logger.info('LoginService.logout:refresh_token_revoked');
      await unitOfWork.commit();
      logger.info('LoginService.logout:success');
      return true;
    } catch (error) {
      logger.error('LoginService.logout:error', { error: error.message });
      await unitOfWork.rollback(error);
      throw error;
    }
  }

  static async validateByRefreshToken(refreshToken) {
    logger.info('LoginService.validateByRefreshToken:start');
    const unitOfWork = new UnitOfWork();
    await unitOfWork.start();
    unitOfWork.registerRepository('login', new LoginRepository(unitOfWork));
    unitOfWork.registerRepository('user', new UserRepository(unitOfWork));
    unitOfWork.registerRepository('status', new UserStatusRepository(unitOfWork));

    try {
      const loginRepo = unitOfWork.getRepository('login');
      const tokenRecord = await loginRepo.findRefreshToken(refreshToken);
      if (!tokenRecord) {
        const error = new Error('Invalid or expired refresh token');
        error.status = 401;
        throw error;
      }

      if (new Date(tokenRecord.expires_at) < new Date()) {
        const error = new Error('Refresh token expired');
        error.status = 401;
        throw error;
      }

      const userRepo = unitOfWork.getRepository('user');
      const user = await userRepo.findUserByEmailOrId(tokenRecord.user_id);
      if (!user) {
        const error = new Error('User not found');
        error.status = 401;
        throw error;
      }

      const statusRepo = unitOfWork.getRepository('status');
      const userStatus = await statusRepo.getStatusByUserId(user.id);
      if (userStatus && userStatus.status === 'suspended') {
        const error = new Error('Account suspended');
        error.status = 403;
        throw error;
      }

      const role = await loginRepo.getUserRole(user.id);
      const permissions = await loginRepo.getUserPermissions(user.id);

      await unitOfWork.commit();

      return {
        user: {
          ...user.getPublicData(),
          role,
          permissions,
        },
      };
    } catch (error) {
      await unitOfWork.rollback(error);
      throw error;
    }
  }
}

// Helper for finding user by id or email
LoginService.findUserByEmailOrId = async function(userIdOrEmail, unitOfWork) {
  logger.info('LoginService.findUserByEmailOrId:start', { userIdOrEmail });
  const userRepo = new UserRepository(unitOfWork);
  try {
    if (typeof userIdOrEmail === 'number') {
      // Find by id
      const query = `SELECT * FROM users WHERE id = ?`;
      const [rows] = await unitOfWork.connection.query(query, [userIdOrEmail]);
      if (!rows.length) {
        logger.warn('LoginService.findUserByEmailOrId:not_found', { userIdOrEmail });
        return null;
      }
      const row = rows[0];
      // You may want to instantiate the correct user type here
      logger.info('LoginService.findUserByEmailOrId:found', { userIdOrEmail });
      return userRepo.findUserByEmail(row.email);
    } else {
      // Find by email
      const user = await userRepo.findUserByEmail(userIdOrEmail);
      logger.info('LoginService.findUserByEmailOrId:found', { userIdOrEmail, found: !!user });
      return user;
    }
  } catch (error) {
    logger.error('LoginService.findUserByEmailOrId:error', { userIdOrEmail, error: error.message });
    throw error;
  }
};

module.exports = LoginService;