const jwt = require("jsonwebtoken");
const config = require('../../config');
const { sequelize, Admin, AdminSession, AdminOTP } = require("../../models");
const speakeasy = require('speakeasy');
const { ValidationError, UnauthorizedError, ConflictError } = require("../../utils/errors");
const logger = require("../../utils/logger");
const { generateRandomString } = require("../../utils/helpers");
const otpService = require("./otpService");


class adminAuthService {
  /* async login(data, req) {
    const { email, password } = data;

    if (!email || !password) throw new ValidationError("email and password are required");

    const admin = await Admin.findOne({
      where: { email: String(email).toLowerCase() },
      include: [{ model: Role, as: "role", include: [{ model: Permission, as: "permissions" }] }],
    });

    if (!admin || !admin.isActive) throw new UnauthorizedError("Invalid credentials");

    const ok = await bcrypt.compare(String(password), admin.passwordHash);
    if (!ok) throw new UnauthorizedError("Invalid credentials");

    await admin.update({ lastLoginAt: new Date() });

    const perms = (admin.role?.permissions || []).map(p => p.key);

    const token = signToken({
      id: admin.id,
      roleId: admin.roleId,
      email: admin.email,
      perms,
    });

    return {
      token,
      admin: {
        id: admin.id,
        email: admin.email,
        roleId: admin.roleId,
        role: admin.role?.name ?? null,
        permissions: perms,
      },
    };
  }
 */

  async login(email, password, ip, device, twoFactorCode = null) {
    // Find admin
    const admin = await Admin.findOne({ where: { email } });
    if (!admin) {
      throw new UnauthorizedError('Email does not exists');
    }

    // Check if account is locked
    if (admin.isLocked()) {
      throw new UnauthorizedError('Account temporarily locked. Please try again later.');
    }

    // Verify password
    const isMatch = await admin.comparePassword(password);
    if (!isMatch) {
      await admin.incrementLoginAttempts();
      throw new UnauthorizedError('Invalid  password');
    }

    // Check account status
    if (admin.status === 'suspended') {
      throw new UnauthorizedError('Account suspended. Please contact support.');
    }

    if (admin.status === 'pending') {
      throw new UnauthorizedError('Please verify your email before logging in.');
    }

    // Check 2FA if enabled
    if (admin.twoFactorEnabled) {
      if (admin.twoFactorMethod === 'email' && !twoFactorCode) {

        await otpService.generateOTP(admin.id, '2fa', ip, device);

      }
      return { requires2FA: true, adminId: admin.id, type: admin.twoFactorMethod };
    }

    // Reset login attempts
    await admin.update({ loginAttempts: 0, lockUntil: null });

    // Generate tokens
    const { accessToken, refreshToken, session } = await this.generateAuthTokens(admin, ip, device);

    // Update last login
    await admin.update({
      lastLogin: new Date(),
      lastLoginIp: ip,
      lastLoginDevice: device,
    });

    logger.info(`Admin logged in: ${email}`);

    return {
      admin: admin.toJSON(),
      accessToken,
      refreshToken,
      sessionId: session.id,
    };
  }

  async verify2fa(email, code, ip, device) {
    if (!email || !code) {
      throw new BadRequestError('Email and OTP code are required');
    }

    const admin = await Admin.findOne({ where: { email } });
    if (!admin) {
      throw new UnauthorizedError('Invalid email');
    }

    if (admin.twoFactorMethod === 'authenticator') {

      if (!admin.twoFactorSecret) {
        throw new BadRequestError('Please setup 2FA first');
      }

      const isValid = speakeasy.totp.verify({
        secret: admin.twoFactorSecret,
        encoding: 'base32',
        token: code,
        window: 1,
      });

      if (!isValid) {
        throw new BadRequestError('Invalid 2FA code');
      }

    }

    if (admin.twoFactorMethod === 'email') {

      const otpres = await otpService.verifyOTP(admin.id, '2fa', code);

      if (!otpres?.status) {
        throw new BadRequestError('Invalid 2FA code');
      }

    }

    // Check if admin exists
    await admin.update({ loginAttempts: 0, lockUntil: null });

    // Generate tokens
    const { accessToken, refreshToken, session } = await this.generateAuthTokens(admin, ip, device);

    // Update last login
    await admin.update({
      lastLogin: new Date(),
      lastLoginIp: ip,
      lastLoginDevice: device,
    });

    logger.info(`Admin logged in: ${email}`);

    return {
      admin: admin.toJSON(),
      accessToken,
      refreshToken,
      sessionId: session.id,
    };
  }

   async resend2fa(email, ip, device) {
    const admin = await Admin.findOne({ where: { email } });

    if (!admin) {
      throw new NotFoundError('Admin not found');
    }

    const otp = await AdminOTP.findOne({
      where: {
        adminId: admin.id,
        type: '2fa',
      },
      order: [['createdAt', 'DESC']],
    });

    if (otp) {
      const now = new Date();
      const diffSeconds = (now - new Date(otp.createdAt)) / 1000;

      if (diffSeconds < 60) {
        throw new BadRequestError('Please wait 60 seconds before resending OTP');
      }
    }

    await AdminOTP.invalidatePreviousOTPs(admin.id, '2fa')

    const token = await otpService.generateOTP(admin.id, '2fa', ip, device);

    return { email: admin.email };
  }

  /**
 * Generate auth tokens and create session
 */
  async generateAuthTokens(admin, ip, device) {
    // Create session
    const session = await AdminSession.create({
      adminId: admin.id,
      token: generateRandomString(64),
      refreshToken: generateRandomString(64),
      ip,
      device,
      expiresAt: new Date(Date.now() + 7 * 24 * 60 * 60 * 1000), // 7 days
    });

    // Generate JWT access token
    const accessToken = jwt.sign(
      { id: admin.id, sessionId: session.id },
      config.jwt.secret,
      { expiresIn: config.jwt.expire }
    );

    // Generate refresh token
    const refreshToken = jwt.sign(
      { id: admin.id, sessionId: session.id, type: 'refresh' },
      config.jwt.refreshSecret,
      { expiresIn: config.jwt.refreshExpire }
    );

    // Update session with tokens
    await session.update({ token: accessToken, refreshToken });

    return { accessToken, refreshToken, session };
  }

  /**
   * Refresh access token
   */
  async refreshToken(refreshToken) {
    try {
      const decoded = jwt.verify(refreshToken, config.jwt.refreshSecret);

      if (decoded.type !== 'refresh') {
        throw new UnauthorizedError('Invalid token type');
      }

      const session = await AdminSession.findByPk(decoded.sessionId, {
        include: [{ model: Admin, as: 'admin' }],
      });

      if (!session || !session.isActive) {
        throw new UnauthorizedError('AdminSession expired');
      }

      if (session.refreshToken !== refreshToken) {
        throw new UnauthorizedError('Invalid refresh token');
      }

      // Generate new access token
      const accessToken = jwt.sign(
        { id: session.admin.id, sessionId: session.id },
        config.jwt.secret,
        { expiresIn: config.jwt.expire }
      );

      await session.update({ token: accessToken, lastActiveAt: new Date() });

      return { accessToken };
    } catch (error) {
      if (error.name === 'JsonWebTokenError' || error.name === 'TokenExpiredError') {
        throw new UnauthorizedError('Invalid refresh token');
      }
      throw error;
    }
  }

  /**
   * Logout admin
   */
  async logout(sessionId) {
    const session = await AdminSession.findByPk(sessionId);
    if (session) {
      await session.revoke();
    }
  }

  /**
   * Logout from all devices
   */
  async logoutAll(adminId) {
    await AdminSession.update(
      { isActive: false, revokedAt: new Date() },
      { where: { adminId, isActive: true } }
    );
  }

  async register(adminData) {
    const {
      name, email, password, role = 'admin', confirmPassword,
    } = adminData;

    // 1️⃣ Check email uniqueness
    const existingAdmin = await Admin.findOne({ where: { email } });
    if (existingAdmin) {
      throw new ConflictError('Admin already registered');
    }

    // 2️⃣ Validate secrets
    if (password !== confirmPassword) {
      throw new ValidationError('Password does not match');
    }


    const createdAdmin = await sequelize.transaction(async (t) => {


      // 5️⃣ Create admin
      const newAdmin = await Admin.create({
        email,
        name,
        role,
        password,
        status: 'active',

      }, { transaction: t });


      return newAdmin;
    });

    logger.info(`New admin registered: ${email}`);

    return {
      admin: createdAdmin.toJSON(),
    };
  }

  async me(adminId) {
    if (!adminId) throw new UnauthorizedError("Not authenticated");

    const admin = await Admin.findByPk(adminId, {
      include: [{ model: Role, as: "role", include: [{ model: Permission, as: "permissions" }] }],
    });

    if (!admin || !admin.isActive) throw new UnauthorizedError("Not authenticated");

    const perms = (admin.role?.permissions || []).map(p => p.key);

    return {
      admin: {
        id: admin.id,
        email: admin.email,
        roleId: admin.roleId,
        role: admin.role?.name ?? null,
        permissions: perms,
        lastLoginAt: admin.lastLoginAt,
      },
    };
  }
}

module.exports = new adminAuthService();
