const jwt = require('jsonwebtoken');
const bcrypt = require('bcryptjs');
const crypto = require('crypto');
const { pool } = require('./db');

const JWT_SECRET = process.env.JWT_SECRET || 'your-super-secret-jwt-key';
const ACCESS_TOKEN_EXPIRES_IN = process.env.ACCESS_TOKEN_EXPIRES_IN || '15m';
const REFRESH_TOKEN_EXPIRES_IN = process.env.REFRESH_TOKEN_EXPIRES_IN || '7d';

// Generate access token (short-lived)
function generateAccessToken(userId, username) {
  return jwt.sign({ userId, username, type: 'access' }, JWT_SECRET, {
    expiresIn: ACCESS_TOKEN_EXPIRES_IN,
  });
}

// Generate refresh token (long-lived)
function generateRefreshToken() {
  return crypto.randomBytes(64).toString('hex');
}

// Store refresh token in database
async function storeRefreshToken(userId, token) {
  const client = await pool.connect();
  try {
    // Calculate expiration date (7 days from now)
    const expiresAt = new Date();
    expiresAt.setDate(expiresAt.getDate() + 7);

    await client.query(
      'INSERT INTO refresh_tokens (user_id, token, expires_at) VALUES ($1, $2, $3)',
      [userId, token, expiresAt]
    );
  } finally {
    client.release();
  }
}

// Verify refresh token
async function verifyRefreshToken(token) {
  const client = await pool.connect();
  try {
    const result = await client.query(
      `SELECT rt.*, u.username
       FROM refresh_tokens rt
       JOIN users u ON rt.user_id = u.id
       WHERE rt.token = $1 AND rt.revoked = FALSE AND rt.expires_at > NOW()`,
      [token]
    );

    if (result.rows.length === 0) {
      throw new Error('Invalid or expired refresh token');
    }

    return result.rows[0];
  } finally {
    client.release();
  }
}

// Revoke refresh token
async function revokeRefreshToken(token) {
  const client = await pool.connect();
  try {
    await client.query(
      'UPDATE refresh_tokens SET revoked = TRUE WHERE token = $1',
      [token]
    );
  } finally {
    client.release();
  }
}

// Revoke all user's refresh tokens
async function revokeAllUserTokens(userId) {
  const client = await pool.connect();
  try {
    await client.query(
      'UPDATE refresh_tokens SET revoked = TRUE WHERE user_id = $1',
      [userId]
    );
  } finally {
    client.release();
  }
}

// Verify access token middleware
function verifyToken(req, res, next) {
  const authHeader = req.headers.authorization;

  if (!authHeader || !authHeader.startsWith('Bearer ')) {
    return res.status(401).json({ error: 'No token provided' });
  }

  const token = authHeader.substring(7);

  try {
    const decoded = jwt.verify(token, JWT_SECRET);

    // Ensure it's an access token
    if (decoded.type !== 'access') {
      return res.status(401).json({ error: 'Invalid token type' });
    }

    req.user = decoded;
    next();
  } catch (error) {
    if (error.name === 'TokenExpiredError') {
      return res.status(401).json({ error: 'Token expired', code: 'TOKEN_EXPIRED' });
    }
    return res.status(401).json({ error: 'Invalid token' });
  }
}

// Register new user
async function register(username, password) {
  const client = await pool.connect();
  try {
    // Check if user already exists
    const existingUser = await client.query(
      'SELECT id FROM users WHERE username = $1',
      [username],
    );

    if (existingUser.rows.length > 0) {
      throw new Error('Username already exists');
    }

    // Hash password
    const hashedPassword = await bcrypt.hash(password, 10);

    // Insert new user
    const result = await client.query(
      'INSERT INTO users (username, password) VALUES ($1, $2) RETURNING id, username',
      [username, hashedPassword],
    );

    const user = result.rows[0];

    // Generate both access and refresh tokens
    const accessToken = generateAccessToken(user.id, user.username);
    const refreshToken = generateRefreshToken();

    // Store refresh token in database
    await storeRefreshToken(user.id, refreshToken);

    return {
      user,
      accessToken,
      refreshToken
    };
  } finally {
    client.release();
  }
}

// Login user
async function login(username, password) {
  const client = await pool.connect();
  try {
    // Find user
    const result = await client.query(
      'SELECT id, username, password FROM users WHERE username = $1',
      [username],
    );

    if (result.rows.length === 0) {
      throw new Error('Invalid credentials');
    }

    const user = result.rows[0];

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

    // Generate both access and refresh tokens
    const accessToken = generateAccessToken(user.id, user.username);
    const refreshToken = generateRefreshToken();

    // Store refresh token in database
    await storeRefreshToken(user.id, refreshToken);

    return {
      user: { id: user.id, username: user.username },
      accessToken,
      refreshToken
    };
  } finally {
    client.release();
  }
}

// Refresh access token using refresh token
async function refreshAccessToken(refreshToken) {
  try {
    // Verify refresh token exists and is valid
    const tokenData = await verifyRefreshToken(refreshToken);

    // Generate new access token
    const accessToken = generateAccessToken(tokenData.user_id, tokenData.username);

    return {
      accessToken,
      user: { id: tokenData.user_id, username: tokenData.username }
    };
  } catch (error) {
    throw new Error('Invalid or expired refresh token');
  }
}

module.exports = {
  generateAccessToken,
  generateRefreshToken,
  storeRefreshToken,
  verifyToken,
  verifyRefreshToken,
  revokeRefreshToken,
  revokeAllUserTokens,
  register,
  login,
  refreshAccessToken,
};

// Made with Bob
