import { Response } from "express";
import { PipelineStage, Types } from "mongoose";
import { hash } from "bcrypt";
import UserModel from "../../models/UserModel";
import ResponseUtil from "../../utils/Response/responseUtils";
import { STATUS_CODES } from "../../constants/statusCodes";
import { CustomRequest } from "../../interfaces/auth";
import {
  adminResetPasswordSchema,
  adminUserUpdateSchema,
} from "../../validators/adminValidators";
import AuthConfig from "../../config/authConfig";
import { ROLE } from "../../constants/enums";
import {
  ADMIN_SORT_LABELS,
  resolveCanonicalRole,
} from "../../utils/adminUserRole";

function escapeRegex(s: string) {
  return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
}

function normalizeUserRecord<T extends { userType?: unknown }>(u: T) {
  const userType = resolveCanonicalRole(u.userType);
  return { ...u, userType, role: userType };
}

export const listUsers = async (req: CustomRequest, res: Response) => {
  try {
    const page = Math.max(parseInt((req.query.page as string) || "1", 10), 1);
    const limit = Math.min(
      Math.max(parseInt((req.query.limit as string) || "20", 10), 1),
      100
    );
    const skip = (page - 1) * limit;
    const q = (req.query.q as string | undefined)?.trim() ?? "";
    const includeAdmins = req.query.includeAdmins === "true";
    const andConditions: Record<string, unknown>[] = [];

    if (q.length > 0) {
      const esc = escapeRegex(q);
      andConditions.push({
        $or: [
          { email: new RegExp(esc, "i") },
          { fullName: new RegExp(esc, "i") },
        ],
      });
    }

    const explicitUserType =
      req.query.userType && String(req.query.userType).trim().length > 0;
    if (explicitUserType) {
      const ut = String(req.query.userType).trim();
      andConditions.push({
        userType: { $regex: new RegExp(`^${escapeRegex(ut)}$`, "i") },
      });
    } else if (!includeAdmins) {
      const adminPattern = ADMIN_SORT_LABELS.map(escapeRegex).join("|");
      andConditions.push({
        userType: {
          $not: new RegExp(`^(${adminPattern})$`, "i"),
        },
      });
    }

    if (req.query.includeDeleted !== "true") {
      andConditions.push({ isDeleted: false });
    }

    const filter: Record<string, unknown> =
      andConditions.length === 1
        ? andConditions[0]!
        : { $and: andConditions };

    const collation = { locale: "en", strength: 2 as const };

    const pipeline: PipelineStage[] = [
      { $match: filter },
      {
        $addFields: {
          _typeLower: { $toLower: { $ifNull: ["$userType", ""] } },
        },
      },
      {
        $addFields: {
          _sortRank: {
            $switch: {
              branches: ADMIN_SORT_LABELS.map((label) => ({
                case: { $eq: ["$_typeLower", label] },
                then: 0,
              })),
              default: {
                $cond: [{ $eq: ["$_typeLower", ROLE.GUEST] }, 1, 2],
              },
            },
          },
        },
      },
      { $sort: { _sortRank: 1, createdAt: -1 } },
      { $skip: skip },
      { $limit: limit },
      { $project: { password: 0, _sortRank: 0, _typeLower: 0 } },
    ];

    const [rawUsers, total] = await Promise.all([
      UserModel.aggregate(pipeline).collation(collation),
      UserModel.countDocuments(filter).collation(collation),
    ]);

    const users = rawUsers.map((u) => normalizeUserRecord(u));

    return ResponseUtil.successResponse(
      res,
      STATUS_CODES.SUCCESS,
      { total, page, limit, totalPages: Math.ceil(total / limit), users },
      "Users list"
    );
  } catch (e) {
    ResponseUtil.handleError(res, e);
  }
};

export const getUserAdmin = async (req: CustomRequest, res: Response) => {
  try {
    const { id } = req.params;
    if (!Types.ObjectId.isValid(id)) {
      return ResponseUtil.errorResponse(
        res,
        STATUS_CODES.BAD_REQUEST,
        "Invalid id"
      );
    }
    const raw = await UserModel.findById(id).select("-password").lean();
    if (!raw) {
      return ResponseUtil.errorResponse(
        res,
        STATUS_CODES.NOT_FOUND,
        "User not found"
      );
    }
    const user = normalizeUserRecord(raw);
    return ResponseUtil.successResponse(
      res,
      STATUS_CODES.SUCCESS,
      { user },
      "User detail"
    );
  } catch (e) {
    ResponseUtil.handleError(res, e);
  }
};

export const updateUserAdmin = async (req: CustomRequest, res: Response) => {
  try {
    const { id } = req.params;
    if (!Types.ObjectId.isValid(id)) {
      return ResponseUtil.errorResponse(
        res,
        STATUS_CODES.BAD_REQUEST,
        "Invalid id"
      );
    }
    const body = await adminUserUpdateSchema.parseAsync(req.body);
    const update: Record<string, unknown> = { ...body };
    if (body.userType === ROLE.ADMIN && id === req.userId) {
      return ResponseUtil.errorResponse(
        res,
        STATUS_CODES.BAD_REQUEST,
        "Use another admin to change your own role"
      );
    }
    const user = await UserModel.findByIdAndUpdate(id, update, {
      new: true,
    })
      .select("-password")
      .lean();
    if (!user) {
      return ResponseUtil.errorResponse(
        res,
        STATUS_CODES.NOT_FOUND,
        "User not found"
      );
    }
    return ResponseUtil.successResponse(
      res,
      STATUS_CODES.SUCCESS,
      { user: normalizeUserRecord(user) },
      "User updated"
    );
  } catch (e) {
    ResponseUtil.handleError(res, e);
  }
};

export const removeUserAdmin = async (req: CustomRequest, res: Response) => {
  try {
    const { id } = req.params;
    if (!Types.ObjectId.isValid(id)) {
      return ResponseUtil.errorResponse(
        res,
        STATUS_CODES.BAD_REQUEST,
        "Invalid id"
      );
    }
    if (id === req.userId) {
      return ResponseUtil.errorResponse(
        res,
        STATUS_CODES.BAD_REQUEST,
        "Cannot delete your own admin account"
      );
    }
    const user = await UserModel.findById(id);
    if (!user) {
      return ResponseUtil.errorResponse(
        res,
        STATUS_CODES.NOT_FOUND,
        "User not found"
      );
    }
    const email = user.email || "";
    await UserModel.findByIdAndUpdate(id, {
      isDeleted: true,
      deletedEmail: email,
      email: `deleted_${id}@removed.local`,
    });
    return ResponseUtil.successResponse(
      res,
      STATUS_CODES.SUCCESS,
      {},
      "User removed (soft delete)"
    );
  } catch (e) {
    ResponseUtil.handleError(res, e);
  }
};

export const resetUserPasswordAdmin = async (
  req: CustomRequest,
  res: Response
) => {
  try {
    const { id } = req.params;
    if (!Types.ObjectId.isValid(id)) {
      return ResponseUtil.errorResponse(
        res,
        STATUS_CODES.BAD_REQUEST,
        "Invalid id"
      );
    }
    const { newPassword } = await adminResetPasswordSchema.parseAsync(
      req.body
    );
    const hashed = await hash(newPassword, String(AuthConfig.SALT));
    const user = await UserModel.findByIdAndUpdate(
      id,
      { password: hashed },
      { new: true }
    ).select("-password");
    if (!user) {
      return ResponseUtil.errorResponse(
        res,
        STATUS_CODES.NOT_FOUND,
        "User not found"
      );
    }
    return ResponseUtil.successResponse(
      res,
      STATUS_CODES.SUCCESS,
      {},
      "Password reset successfully"
    );
  } catch (e) {
    ResponseUtil.handleError(res, e);
  }
};
