import { Request, Response } from "express";
import { compareSync, hash } from "bcrypt";
import { randomInt } from "crypto";
import { Types } from "mongoose";
import UserModel from "../../models/UserModel";
import ResponseUtil from "../../utils/Response/responseUtils";
import { STATUS_CODES } from "../../constants/statusCodes";
import { signupSchema } from "../../validators/authValidators";
import {
  adminForgotPasswordSchema,
  adminResendOtpSchema,
  adminResetPasswordSchema,
  adminVerifyResetOtpSchema,
} from "../../validators/adminAuthValidators";
import { generateToken } from "../../utils/Token";
import { ROLE } from "../../constants/enums";
import { isAdminUserType } from "../../utils/adminUserRole";
import AuthConfig from "../../config/authConfig";
import { OTP_SEND_REASON } from "../../constants/otp";
import {
  emailTemplateGeneric,
  otpEmailSubject,
} from "../../utils/SendEmail/templates";
import { sendEmail } from "../../utils/SendEmail";
import {
  assertOtpValid,
  clearOtpsForUser,
  saveOtpForUser,
} from "../../services/otpService";

const ADMIN_AUTH = {
  LOGIN_OK: "Admin login successful",
  INVALID_CREDENTIALS: "Invalid credentials",
  PASSWORD_LOGIN_UNAVAILABLE: "Password login not available for this account",
  NOT_VERIFIED: "Account is not verified",
  SUSPENDED: "Account is suspended",
  NOT_FOUND: "Admin account not found",
  OTP_SENT: "Password reset code sent. Check your email to continue.",
  OTP_RESENT: "Verification code resent. Check your email.",
  OTP_VERIFIED: "Code verified. You can set a new password now.",
  PASSWORD_RESET: "Password has been successfully updated",
} as const;

type AdminUserDoc = InstanceType<typeof UserModel>;

async function findEligibleAdmin(opts: {
  email?: string;
  userId?: string;
}): Promise<AdminUserDoc | null> {
  const filter: Record<string, unknown> = { isDeleted: false };
  if (opts.userId) {
    if (!Types.ObjectId.isValid(opts.userId)) return null;
    filter._id = new Types.ObjectId(opts.userId);
  } else if (opts.email) {
    filter.email = opts.email.trim().toLowerCase();
  } else {
    return null;
  }

  const user = await UserModel.findOne(filter);
  if (!user || !isAdminUserType(user.userType)) return null;
  if (user.isBanned) return null;
  return user;
}

async function sendAdminResetOtp(user: AdminUserDoc): Promise<boolean> {
  const otp = randomInt(100000, 999999);
  const reason = OTP_SEND_REASON.FORGOT_PASSWORD;
  await saveOtpForUser(user._id, otp, reason);
  const template = emailTemplateGeneric(otp, reason);
  return sendEmail(String(user.email), otpEmailSubject(reason), template);
}

export const adminLogin = async (req: Request, res: Response) => {
  try {
    const { email, password } = await signupSchema.parseAsync(req.body);
    const user = await UserModel.findOne({ email, isDeleted: false });

    if (!user || !isAdminUserType(user.userType)) {
      return ResponseUtil.errorResponse(
        res,
        STATUS_CODES.UNAUTHORIZED,
        ADMIN_AUTH.INVALID_CREDENTIALS,
      );
    }

    if (!user.password) {
      return ResponseUtil.errorResponse(
        res,
        STATUS_CODES.BAD_REQUEST,
        ADMIN_AUTH.PASSWORD_LOGIN_UNAVAILABLE,
      );
    }

    if (!compareSync(password, String(user.password))) {
      return ResponseUtil.errorResponse(
        res,
        STATUS_CODES.UNAUTHORIZED,
        ADMIN_AUTH.INVALID_CREDENTIALS,
      );
    }

    if (!user.isVerified) {
      return ResponseUtil.errorResponse(
        res,
        STATUS_CODES.BAD_REQUEST,
        ADMIN_AUTH.NOT_VERIFIED,
      );
    }

    if (user.isBanned) {
      return ResponseUtil.errorResponse(
        res,
        STATUS_CODES.FORBIDDEN,
        ADMIN_AUTH.SUSPENDED,
      );
    }

    const token = generateToken({
      email: String(user.email),
      id: String(user._id),
      role: ROLE.ADMIN,
    });

    const safe = user.toObject();
    delete (safe as { password?: string }).password;

    return ResponseUtil.successResponse(
      res,
      STATUS_CODES.SUCCESS,
      { user: safe, token },
      ADMIN_AUTH.LOGIN_OK,
    );
  } catch (e) {
    ResponseUtil.handleError(res, e);
  }
};

/**
 * POST /api/v1/admin/forgot-password
 * Send password-reset OTP to an admin email.
 */
export const adminForgotPassword = async (req: Request, res: Response) => {
  try {
    const { email } = await adminForgotPasswordSchema.parseAsync(req.body);
    const user = await findEligibleAdmin({ email });

    if (!user) {
      return ResponseUtil.errorResponse(
        res,
        STATUS_CODES.NOT_FOUND,
        ADMIN_AUTH.NOT_FOUND,
      );
    }

    const emailed = await sendAdminResetOtp(user);
    if (!emailed) {
      return ResponseUtil.errorResponse(
        res,
        STATUS_CODES.INTERNAL_SERVER_ERROR,
        "Failed to send email. Please try again later.",
      );
    }

    return ResponseUtil.successResponse(
      res,
      STATUS_CODES.SUCCESS,
      { userId: String(user._id), email: String(user.email) },
      ADMIN_AUTH.OTP_SENT,
    );
  } catch (e) {
    ResponseUtil.handleError(res, e);
  }
};

/**
 * POST /api/v1/admin/resend-otp
 * Resend forgot-password OTP (by userId or email).
 */
export const adminResendOtp = async (req: Request, res: Response) => {
  try {
    const body = await adminResendOtpSchema.parseAsync(req.body);
    const user = await findEligibleAdmin({
      userId: body.userId,
      email: body.email,
    });

    if (!user) {
      return ResponseUtil.errorResponse(
        res,
        STATUS_CODES.NOT_FOUND,
        ADMIN_AUTH.NOT_FOUND,
      );
    }

    const emailed = await sendAdminResetOtp(user);
    if (!emailed) {
      return ResponseUtil.errorResponse(
        res,
        STATUS_CODES.INTERNAL_SERVER_ERROR,
        "Failed to send email. Please try again later.",
      );
    }

    return ResponseUtil.successResponse(
      res,
      STATUS_CODES.SUCCESS,
      { userId: String(user._id), email: String(user.email) },
      ADMIN_AUTH.OTP_RESENT,
    );
  } catch (e) {
    ResponseUtil.handleError(res, e);
  }
};

/**
 * POST /api/v1/admin/verify-reset-otp
 * Validate OTP before showing the reset-password screen.
 */
export const adminVerifyResetOtp = async (req: Request, res: Response) => {
  try {
    const { userId, otp } = await adminVerifyResetOtpSchema.parseAsync(
      req.body,
    );
    const user = await findEligibleAdmin({ userId });

    if (!user) {
      return ResponseUtil.errorResponse(
        res,
        STATUS_CODES.NOT_FOUND,
        ADMIN_AUTH.NOT_FOUND,
      );
    }

    const check = await assertOtpValid(user._id as Types.ObjectId, otp);
    if (!check.ok) {
      return ResponseUtil.errorResponse(
        res,
        STATUS_CODES.BAD_REQUEST,
        check.message,
      );
    }

    return ResponseUtil.successResponse(
      res,
      STATUS_CODES.SUCCESS,
      { userId, otpVerified: true },
      ADMIN_AUTH.OTP_VERIFIED,
    );
  } catch (e) {
    ResponseUtil.handleError(res, e);
  }
};

/**
 * POST /api/v1/admin/reset-password
 * Set a new password after OTP validation (OTP checked again here).
 */
export const adminResetPassword = async (req: Request, res: Response) => {
  try {
    const { userId, otp, password } = await adminResetPasswordSchema.parseAsync(
      req.body,
    );
    const user = await findEligibleAdmin({ userId });

    if (!user) {
      return ResponseUtil.errorResponse(
        res,
        STATUS_CODES.NOT_FOUND,
        ADMIN_AUTH.NOT_FOUND,
      );
    }

    const check = await assertOtpValid(user._id as Types.ObjectId, otp);
    if (!check.ok) {
      return ResponseUtil.errorResponse(
        res,
        STATUS_CODES.BAD_REQUEST,
        check.message,
      );
    }

    const saltRounds = Number(AuthConfig.SALT) || 10;
    user.password = await hash(password, saltRounds);
    if (!user.isVerified) {
      user.isVerified = true;
    }
    await user.save();
    await clearOtpsForUser(user._id as Types.ObjectId);

    return ResponseUtil.successResponse(
      res,
      STATUS_CODES.SUCCESS,
      {},
      ADMIN_AUTH.PASSWORD_RESET,
    );
  } catch (e) {
    ResponseUtil.handleError(res, e);
  }
};
