import httpStatus from 'http-status';
import mongoose, { Types } from 'mongoose';

import {
  getUserByEmail,
  getUserById,
  updateUserById,
} from '@/modules/user/user.service';
import ApiError from '@/shared/utils/errors/ApiError';
import { generateAuthTokens, verifyToken } from '@/modules/token/token.service';
import {
  IUserDoc,
  IUserWithTokens,
  UpdateUserBody,
} from '@/modules/user/user.interfaces';
import responseCodes from '@/shared/utils/responseCode/responseCode';
import { getObjectId } from '@/shared/utils/commonHelper';
import { AuthenticatedUserResponse } from '@/modules/auth/auth.interfaces';
import { resendOtp, sendOtp, verifyOtp } from '@/modules/otp/otp.service';
import User from '@/modules/user/user.model';
import { OtpPayload } from '@/modules/otp/otp.interface';
import { CONSTANTS } from '@/shared/constants';
import {
  buildAuthResponse,
  sendSetPasswordEmail,
  sendUserEmail,
} from '@/modules/auth/auth.helper';
import { Company } from '@/modules/company/company.model';
import { Status } from '@/shared/constants/enum.constant';
import { ICompany } from '@/modules/company/company.interface';
import { defaultStatus } from '@/shared/utils/responseCode/httpStatusAlias';

const { UserResponseCodes } = responseCodes;

const DUMMY_TEST_NUMBERS = [
  { dialCode: 91, phone: 1234567890 },
  { dialCode: 91, phone: 9999999999 },
  { dialCode: 91, phone: 8888888888 },
  { dialCode: 91, phone: 7067410528 },
  { dialCode: 91, phone: 9723279771 },
  { dialCode: 91, phone: 9687005994 },
  { dialCode: 91, phone: 6354356538 },
  { dialCode: 91, phone: 2428184536 },
];

/**
 * Login with username and password
 * @param {string} email
 * @param {string} password
 * @returns {Promise<IUserDoc>}
 */
export const loginUserWithEmailAndPassword = async (
  email: string,
  password: string,
  deviceToken?: string,
): Promise<AuthenticatedUserResponse> => {
  console.log('[LOGIN] Attempting login for email:', email);
  let user;
  try {
    user = await getUserByEmail(email);
    console.log('[LOGIN] User found:', user ? user.email : null);
  } catch (err) {
    console.error('[LOGIN] Error during user lookup:', err);
    throw err;
  }

  if (user.status === CONSTANTS.INACTIVE) {
    console.warn('[LOGIN] User inactive:', user.email);
    throw new ApiError(
      httpStatus.OK,
      'Your account is got inactivated. Please contact support.',
      true,
      '',
      UserResponseCodes.USER_ALREADY_INACTIVE,
    );
  }

  let passwordMatch = false;
  try {
    passwordMatch = await user.isPasswordMatch(password);
    console.log('[LOGIN] Password match:', passwordMatch);
  } catch (err) {
    console.error('[LOGIN] Error during password check:', err);
    throw err;
  }

  if (!user || !passwordMatch) {
    console.warn('[LOGIN] Incorrect email or password for:', email);
    throw new ApiError(
      httpStatus.OK,
      'Incorrect email or password',
      true,
      '',
      UserResponseCodes.INCORRECT_EMAIL_OR_PASSWORD,
    );
  }

  user.updatedAt = new Date();

  if (deviceToken && !user.deviceToken.includes(deviceToken)) {
    user.deviceToken.push(deviceToken);
    await user.save();
  }

  console.log('[LOGIN] Login successful for:', user.email);
  return buildAuthResponse(user);
};

/**
 * Logout
 * @param {string} refreshToken
 * @returns {Promise<void>}
 */
export const logout = async (
  userId: string,
  deviceToken: IUserDoc,
): Promise<void> => {
  const user = await User.findByIdAndUpdate(
    userId,
    {
      $pull: {
        deviceTokens: deviceToken,
      },
    },
    {
      new: true,
      runValidators: true,
    },
  );

  if (!user) throw new ApiError(httpStatus.NOT_FOUND, 'User not found');
};

/**
 * Refresh auth tokens
 * @param {string} refreshToken
 * @returns {Promise<IUserWithTokens>}
 */
export const refreshAuth = async (
  refreshToken: string,
): Promise<IUserWithTokens> => {
  try {
    const refreshTokenDoc = await verifyToken(refreshToken);

    const user = await getUserById(
      new mongoose.Types.ObjectId(refreshTokenDoc.user),
    );
    if (!user) throw new Error();

    const tokens = await generateAuthTokens({
      id: user._id as Types.ObjectId,
      companyId: user.company.id._id as Types.ObjectId,
    });
    return { user, tokens };
  } catch (error) {
    if (error instanceof ApiError) throw error;
    throw new ApiError(
      defaultStatus.UNAUTHORIZED,
      'Please authenticate',
      true,
      '',
      UserResponseCodes.PLEASE_AUTHENTICATE,
    );
  }
};

/**
 * Reset password
 * @param {string} resetPasswordToken
 * @param {string} newPassword
 * @returns {Promise<void>}
 */
export const resetPassword = async (
  token: string,
  newPassword: string,
): Promise<void> => {
  try {
    const resetPasswordTokenDoc = await verifyToken(token);

    const userPopulate = 'team:name';

    const userId = getObjectId(resetPasswordTokenDoc.sub);

    const companyId = getObjectId(resetPasswordTokenDoc.companyId);
    const user = await getUserById(userId, undefined, userPopulate);

    const updatePayload: UpdateUserBody = { password: newPassword };

    if (!user.isEmailVerified) updatePayload.isEmailVerified = true;

    if (user.status !== Status.ACTIVE) updatePayload.status = Status.ACTIVE;

    if (!user.resetTokenVersion) updatePayload.resetTokenVersion = 1;
    else user.resetTokenVersion += 1;

    await user.save();

    await updateUserById(user.id, updatePayload);

    let company;

    if (companyId) {
      company = await Company.findById(companyId);
      if (company && company.status !== Status.ACTIVE)
        await Company.findByIdAndUpdate(companyId, {
          status: Status.ACTIVE,
        });
    }
    await sendUserEmail(user, company);
  } catch (error) {
    console.log('🚀 ~ resetPassword ~ error:', error);
    if (error instanceof ApiError) throw error;
    throw new ApiError(
      httpStatus.OK,
      'Password reset failed',
      true,
      '',
      UserResponseCodes.PASSWORD_RESET_FAILED,
    );
  }
};

/**
 * Verify email
 * @param {string} verifyEmailToken
 * @returns {Promise<IUserDoc | null>}
 */
export const verifyEmail = async (
  verifyEmailToken: string,
): Promise<IUserDoc | null> => {
  try {
    const verifyEmailTokenDoc = await verifyToken(verifyEmailToken);
    const user = await getUserById(
      new mongoose.Types.ObjectId(verifyEmailTokenDoc.user),
    );
    if (!user) throw new Error();

    const updatedUser = await updateUserById(user.id, {
      isEmailVerified: true,
    });
    return updatedUser;
  } catch (_error) {
    throw new ApiError(httpStatus.UNAUTHORIZED, 'Email verification failed');
  }
};

export const sendPasswordEmail = async (userId: string, type?: string) => {
  console.log('🚀 ~ sendPasswordEmail ~ type:', type);
  const user = await User.findById(userId)
    .populate({ path: 'company.id', select: 'name' })
    .populate('createdBy');

  if (!user)
    throw new ApiError(
      httpStatus.OK,
      'User Not found',
      true,
      '',
      UserResponseCodes.USER_NOT_FOUND,
    );

  const emailSent = await sendSetPasswordEmail({
    user: {
      id: user._id,
      firstName: user.firstName,
      lastName: user.lastName,
      userType: user.userType,
      email: user.email,
    },
    company: {
      id: user.company.id._id,
      name: (user.company.id as unknown as ICompany).name,
    },
    type,
    createdBy: user.createdBy,
  });

  return emailSent;
};

// APP USER SERVICE

/**
 * Create a user using phone number
 * @param {Object} userBody
 * @returns {Promise<User>}
 */
export const generateAppUserOTP = async (userBody: OtpPayload) => {
  const { dialCode, phone } = userBody;
  const existingUser = await User.findOne({
    'phone.dialCode': dialCode,
    'phone.number': phone,
  });

  if (!existingUser)
    throw new ApiError(
      httpStatus.BAD_REQUEST,
      'User not found',
      true,
      '',
      UserResponseCodes.USER_NOT_FOUND,
    );

  if (existingUser.status !== CONSTANTS.ACTIVE)
    throw new ApiError(
      httpStatus.BAD_REQUEST,
      'Your account is suspended. Please contact support.',

      true,
      '',
      UserResponseCodes.ACCOUNT_SUSPENDED,
    );

  const isTestNumber = DUMMY_TEST_NUMBERS.some(
    (entry) => entry.dialCode === dialCode && entry.phone === phone,
  );

  if (isTestNumber) return 'test-order-id';

  const number = Number(`${dialCode}${phone}`);
  const orderId = await sendOtp(
    number,
    existingUser.firstName,
    existingUser.company.id,
  );

  return orderId;
};

/**
 * Resend OTP to phone number
 * @param {Object} userBody
 * @returns {String<orderId>}
 */
export const resendOtpToPhone = async (userBody: OtpPayload) => {
  const orderId = await resendOtp(userBody.orderId);
  return orderId;
};

/**
 * Verify OTP related phone number
 * @param {Object} userBody
 * @returns {Boolean<verified>}
 */
export const verifyPhoneOtp = async (userBody: OtpPayload) => {
  const { phone, dialCode } = userBody;
  // let verified: boolean;

  let userInfo = await User.findOne({
    'phone.dialCode': dialCode,
    'phone.number': phone,
  });

  if (!userInfo)
    throw new ApiError(
      httpStatus.NOT_FOUND,
      'User not found',

      true,
      '',
      UserResponseCodes.USER_NOT_FOUND,
    );

  // if (phone === 1234567890 && dialCode === 91) verified = true;
  // else verified = await verifyOtp(userBody);

  const isTestNumber = DUMMY_TEST_NUMBERS.some(
    (entry) => entry.dialCode === dialCode && entry.phone === phone,
  );

  const verified = isTestNumber ? true : await verifyOtp(userBody);

  if (verified) {
    // Update the user's deviceId
    if (
      userBody.deviceToken &&
      !userInfo.deviceToken.includes(userBody.deviceToken)
    ) {
      userInfo.deviceToken.push(userBody.deviceToken);
      userInfo.deviceType = userBody.deviceType;
      await userInfo.save();
    }

    await userInfo.populate({
      path: 'company.id',
      select: '_id companyType name',
    });

    return buildAuthResponse(userInfo);
  }
};
