const bcrypt = require('bcryptjs');
const jwt = require('jsonwebtoken');
const { User } = require('../models');
const { ApiError } = require('../utils/apiError');
const logger = require('../utils/logger');
const sequelize = require('../config/database');
const { redisClient } = require('../config/redis');

class AuthService {
  static async register({ email, password, name }) {
    const transaction = await sequelize.transaction();
    try {
      const existingUser = await User.findOne({ 
        where: { email }, 
        transaction,
        attributes: ['id']
      });

      if (existingUser) {
        throw new ApiError(409, 'Email already registered');
      }

      const salt = await bcrypt.genSalt(12);
      const hashedPassword = await bcrypt.hash(password, salt);
      
      const user = await User.create(
        { 
          email, 
          password: hashedPassword, 
          name 
        },
        { transaction }
      );

      await transaction.commit();
      logger.info(`New user registered: ${user.id}`);
      
      return { 
        id: user.id, 
        email: user.email, 
        name: user.name 
      };
    } catch (error) {
      await transaction.rollback();
      logger.error('Registration error:', { error: error.message, stack: error.stack });
      throw error;
    }
  }

  static async login({ email, password }) {
    const user = await User.findOne({
      where: { email },
      attributes: ['id', 'email', 'password', 'name']
    });

    if (!user) {
      throw new ApiError(401, 'Invalid credentials');
    }

    const isValidPassword = await bcrypt.compare(password, user.password);
    if (!isValidPassword) {
      throw new ApiError(401, 'Invalid credentials');
    }

    await User.update(
      { lastLogin: new Date() },
      { 
        where: { id: user.id },
        returning: false
      }
    );

    const tokens = await this.generateTokens(user);
    await this.storeRefreshToken(user.id, tokens.refreshToken);

    logger.info(`User logged in: ${user.id}`);
    return tokens;
  }

  static async refreshToken(refreshToken) {
    try {
      const decoded = jwt.verify(refreshToken, process.env.REFRESH_TOKEN_SECRET);
      const storedToken = await redisClient.get(`refresh_token:${decoded.id}`);

      if (!storedToken || storedToken !== refreshToken) {
        throw new ApiError(401, 'Invalid refresh token');
      }

      const user = await User.findByPk(decoded.id, {
        attributes: ['id', 'email', 'name']
      });
      
      if (!user) {
        throw new ApiError(401, 'User not found');
      }
      
      const tokens = await this.generateTokens(user);
      await this.storeRefreshToken(user.id, tokens.refreshToken);

      logger.info(`Token refreshed for user: ${user.id}`);
      return tokens;
    } catch (error) {
      logger.error('Token refresh error:', { error: error.message, stack: error.stack });
      throw new ApiError(401, 'Invalid refresh token');
    }
  }

  static async generateTokens(user) {
    const accessToken = jwt.sign(
      { 
        id: user.id, 
        email: user.email 
      },
      process.env.ACCESS_TOKEN_SECRET,
      { expiresIn: process.env.ACCESS_TOKEN_EXPIRES_IN || '15m' }
    );

    const refreshToken = jwt.sign(
      { id: user.id },
      process.env.REFRESH_TOKEN_SECRET,
      { expiresIn: process.env.REFRESH_TOKEN_EXPIRES_IN || '7d' }
    );

    return { accessToken, refreshToken };
  }

  static async storeRefreshToken(userId, refreshToken) {
    await redisClient.setEx(
      `refresh_token:${userId}`,
      7 * 24 * 60 * 60,
      refreshToken
    );
  }
}

module.exports = AuthService;