const path = require('path');
require('dotenv').config();
const { models } = require(path.resolve(process.env.ROOT_PATH, './db/db.js'));
const BaseController = require(path.resolve(process.env.ROOT_PATH, './controllers/BaseController.js'));
const jwt = require('jsonwebtoken');
const bcrypt = require('bcrypt');
const crypto = require('crypto');
const { format, differenceInHours, addHours } = require('date-fns');
const sendMail = require('../utils/sendMail.js');
const HaveIBeenPwnedAPI = require('../utils/hibp.js');

class AuthController extends BaseController {
  constructor() {
    super();

    this.protected('me', 'refreshToken', 'login');
  }

  /**
   * @swagger
   * /auth/login:
   *   post:
   *     summary: Authenticate a user
   *     tags: [Authentication]
   *     requestBody:
   *       required: true
   *       content:
   *         application/json:
   *           schema:
   *             type: object
   *             required:
   *               - username
   *               - password
   *             properties:
   *               username:
   *                 type: string
   *                 description: Username or email
   *               password:
   *                 type: string
   *                 format: password
   *     responses:
   *       200:
   *         description: Login successful
   *         content:
   *           application/json:
   *             schema:
   *               type: object
   *               properties:
   *                 api_key:
   *                   type: string
   *                   description: JWT token for authentication
   *       401:
   *         description: Invalid credentials
   */
  async login(req, res) {
    const { username, password } = req.body;
    const user = await models.User.query().where('username', username).orWhere('email', username).first();
    if (!user) {
      return res.status(401).json({ error: 'Invalid username or password' });
    }
    const passwordMatch = await bcrypt.compare(password, user.password);
    if (!passwordMatch) {
      return res.status(401).json({ error: 'Invalid username or password' });
    }

    const validToken = jwt.verify(user.api_key, process.env.JWT_SECRET);
    if (!validToken) {
      const token = jwt.sign({ id: user.id, username: user.username, is_admin: user.is_admin, email: user.email }, process.env.JWT_SECRET, { expiresIn: '6h' });
      await models.User.query().update({ api_key: token, last_login: format(new Date(), 'yyyy-MM-dd HH:mm:ss') }).where('id', user.id);
      return res.json({ api_key: token });
    }

    await models.User.query().update({ last_login: format(new Date(), 'yyyy-MM-dd HH:mm:ss') }).where('id', user.id);
    res.json({ api_key: user.api_key });
  }

  /**
   * @swagger
   * /auth/me:
   *   get:
   *     summary: Get current user details
   *     tags: [Authentication]
   *     security:
   *       - bearerAuth: []
   *     responses:
   *       200:
   *         description: User details
   *         content:
   *           application/json:
   *             schema:
   *               type: object
   *               properties:
   *                 id:
   *                   type: integer
   *                 username:
   *                   type: string
   *                 email:
   *                   type: string
   *                 is_admin:
   *                   type: boolean
   *                 is_active:
   *                   type: boolean
   *                 created:
   *                   type: string
   *                   format: date-time
   *                 modified:
   *                   type: string
   *                   format: date-time
   *                 token_expiration:
   *                   type: string
   *                   format: date-time
   *                 is_token_expired:
   *                   type: boolean
   *       401:
   *         description: Unauthorized
   */
  async me(req, res) {
    const user = await models.User.query().findById(req.user.id);
    
    const token = req.headers.authorization?.split(' ')[1];
    
    const decodedToken = token ? jwt.decode(token) : null;
    const tokenExpiration = decodedToken?.exp ? new Date(decodedToken.exp * 1000) : null;
    const isTokenExpired = tokenExpiration ? tokenExpiration < new Date() : true;
    
    res.json({
      id: user.id,
      username: user.username,
      email: user.email,
      is_admin: user.is_admin,
      is_active: user.is_active,
      created: user.created,
      modified: user.modified,
      token_expiration: tokenExpiration,
      is_token_expired: isTokenExpired
    });
  }

  /**
   * @swagger
   * /auth/refresh-token:
   *   post:
   *     summary: Refresh authentication token
   *     tags: [Authentication]
   *     security:
   *       - bearerAuth: []
   *     responses:
   *       200:
   *         description: Token refreshed successfully
   *         content:
   *           application/json:
   *             schema:
   *               type: object
   *               properties:
   *                 api_key:
   *                   type: string
   *                   description: New JWT token
   *       401:
   *         description: Unauthorized
   */
  async refreshToken(req, res) {
    const user = await models.User.query().findById(req.user.id);
    const token = jwt.sign({ id: user.id, username: user.username, is_admin: user.is_admin, email: user.email }, process.env.JWT_SECRET, { expiresIn: '6h' });
    res.json({ api_key: token });
  }

  async registerView(req, res) {
    res.render('auth/register');
  }

  async register(req, res) {
    console.log(req.body);

    const { username, email, password } = req.body;

    const hibp = new HaveIBeenPwnedAPI();

    try {
      const checkBreached = await hibp.checkPassword(password);
      if (checkBreached.isCompromised) {
        return res.status(400).json({ error: 'Password is compromised. Checked against haveibeenpwned.com' });
      }

      const validatePassword = await hibp.validatePassword(password, { maxExposures: 0 });
      if (!validatePassword.isValid) {
        return res.status(400).json({ error: 'Password is not valid. Checked against haveibeenpwned.com' });
      }
    } catch (error) {
      console.error(error);
      return res.status(500).json({ error: 'Failed to check password' });
    }

    const dupUser = await models.User.query().where('username', username).orWhere('email', email).first();
    if (dupUser) {
      return res.status(400).json({ error: 'Username or email already exists' });
    }

    const invite_token = crypto.randomBytes(32).toString('hex');

    const salt = await bcrypt.genSalt(10);
    const passwordEncrypted = await bcrypt.hash(password, salt);
    const newEmail = `${username}@${crypto.randomBytes(10).toString('hex')}.com`;
    const user = await models.User.query().insert({
      username,
      password: passwordEncrypted,
      email: newEmail,
      is_admin: 0,
      is_active: 0,
      created: format(new Date(), 'yyyy-MM-dd HH:mm:ss'),
      modified: format(new Date(), 'yyyy-MM-dd HH:mm:ss'),
      last_login: format(new Date(), 'yyyy-MM-dd HH:mm:ss')
    });

    await models.Invite.query().insert({
      user_id: user.id,
      token: invite_token,
      expires: format(addHours(new Date(), 12), 'yyyy-MM-dd HH:mm:ss'),
      created: format(new Date(), 'yyyy-MM-dd HH:mm:ss')
    });

    await sendMail(
        email,
        '2weekmail - Invite Code',
        `Welcome to the 2weekmail. Please use the link below to activate your account.
        <br>
        <a href="https://2weekmail.fyi/auth/activate/${invite_token}">activate your account</a>`,
        `Welcome to the 2weekmail. Please use it to <a href="https://2weekmail.fyi/auth/activate/${invite_token}">activate your account</a>.`
    );

    res.json({
      message: 'User registered successfully'
    });
  }

  async activateView(req, res) {
    res.render('auth/activate');
  }

  async activate(req, res) {
    const invite_token = req.params.token;
    const invite = await models.Invite.query().where('token', invite_token).first();
    if (!invite) {
      return res.status(400).json({ error: 'Invalid invite token' });
    }

    if (differenceInHours(new Date(), new Date(invite.expires_at)) > 0) {
      return res.status(400).json({ error: 'Invite token expired' });
    }

    const user = await models.User.query().where('id', invite.user_id).first();
    if (!user) {
      return res.status(400).json({ error: 'User not found' });
    }

    await models.User.query().update({
      is_active: 1,
      modified: format(new Date(), 'yyyy-MM-dd HH:mm:ss'),
      last_login: format(new Date(), 'yyyy-MM-dd HH:mm:ss')
    }).where('id', user.id);

    await models.Invite.query().delete().where('id', invite.id);

    return res.json({
      success: true,
      message: 'User activated successfully'
    });
  }
}

module.exports = new AuthController();