import models from "$models/index.js";
import { redisConnection } from "$connections/index.js";
import { authHelpers, clubHelpers, identityHelpers } from "$helpers/index.js";
import { sejamServices, smsServices } from "$services/index.js";

import { telegramBotConfig } from "./config.js";
import { enforceBotRateLimit } from "./state.js";

const normalizeTelegramId = (value) => String(value || "").trim();

const ensureBotDevelopmentAccess = (identifier) => {
  if (!telegramBotConfig.developmentMode) {
    return;
  }

  if (!telegramBotConfig.allowedNationalCodes.includes(String(identifier))) {
    const error = new Error("این کد ملی در محیط توسعه ربات مجاز نیست.");
    error.status = 403;
    throw error;
  }
};

const resolveIdentifier = (value) => {
  const identifier = identityHelpers.normalizeIdentifier(value);
  const personType = identifier.length === 11 ? "legal" : "private";
  const validation = identityHelpers.validateIdentifierByPersonType({
    personType,
    identifier,
  });

  if (!validation.isValid) {
    const error = new Error(validation.message);
    error.status = 422;
    throw error;
  }

  return {
    identifier: validation.normalizedIdentifier,
    personType,
    isPrivatePerson: validation.isPrivatePerson,
  };
};

const findUserByIdentifier = ({ identifier, isPrivatePerson }) =>
  models.user
    .findOne(
      isPrivatePerson ? { nationalId: identifier } : { businessID: identifier },
    )
    .populate("role");

const hasCompleteSejamProfile = async (user) => {
  if (!user?._id || user.isSejamVerified !== true) {
    return false;
  }

  const userInfo = await models.userInfo
    .findOne({ user: user._id })
    .select("_id")
    .lean();
  return Boolean(userInfo?._id);
};

const storeInternalOtp = async ({ identifier, user }) => {
  if (!user.mobile) {
    const error = new Error("شماره موبایل کاربر ثبت نشده است.");
    error.status = 422;
    throw error;
  }

  const otp = authHelpers.isDevelopmentUsers(user)
    ? authHelpers.DEV_USERS_OTP
    : authHelpers.generateOtp();

  await redisConnection.setex(
    authHelpers.getOtpKey(identifier),
    authHelpers.OTP_TTL_SECONDS,
    otp,
  );

  if (!authHelpers.isDevelopmentUsers(user)) {
    await smsServices.sendOTPSms({
      mobile: user.mobile,
      message: otp,
    });
  }
};

export const requestTelegramAuthenticationOtp = async ({
  telegramId,
  rawIdentifier,
}) => {
  const normalizedTelegramId = normalizeTelegramId(telegramId);
  const identity = resolveIdentifier(rawIdentifier);

  ensureBotDevelopmentAccess(identity.identifier);

  await Promise.all([
    enforceBotRateLimit({
      scope: "otp-send-telegram",
      identifier: normalizedTelegramId,
      max: 5,
      windowSeconds: 15 * 60,
    }),
    enforceBotRateLimit({
      scope: "otp-send-identifier",
      identifier: identity.identifier,
      max: 5,
      windowSeconds: 15 * 60,
    }),
  ]);

  const telegramOwner = await models.user
    .findOne({
      telegramId: normalizedTelegramId,
      isTelegramVerified: true,
    })
    .select("_id");

  if (telegramOwner) {
    const error = new Error(
      "این حساب تلگرام قبلاً به یک حساب کاربری متصل شده است.",
    );
    error.status = 409;
    throw error;
  }

  const user = await findUserByIdentifier(identity);
  if (
    user?.telegramId &&
    String(user.telegramId) !== normalizedTelegramId &&
    user.isTelegramVerified
  ) {
    const error = new Error(
      "این حساب کاربری قبلاً به حساب تلگرام دیگری متصل شده است.",
    );
    error.status = 409;
    throw error;
  }

  let authMode;
  if (await hasCompleteSejamProfile(user)) {
    await storeInternalOtp({ identifier: identity.identifier, user });
    authMode = "internal";
  } else if (
    authHelpers.shouldUseMockSejamFlow({
      identifier: identity.identifier,
      userExists: Boolean(user),
    })
  ) {
    await redisConnection.setex(
      authHelpers.getOtpKey(identity.identifier),
      authHelpers.OTP_TTL_SECONDS,
      authHelpers.DEV_USERS_OTP,
    );
    authMode = "mock";
  } else {
    await sejamServices.requestOtp(identity.identifier);
    authMode = "sejam";
  }

  return {
    ...identity,
    authMode,
    userId: user?._id ? String(user._id) : "",
  };
};

const verifyAuthenticationOtp = async ({
  identifier,
  isPrivatePerson,
  otp,
  authMode,
}) => {
  let user = await findUserByIdentifier({ identifier, isPrivatePerson });

  if (authMode === "internal") {
    await authHelpers.validateUserOtp(identifier, otp);
    await redisConnection.del(authHelpers.getOtpKey(identifier));
    return user;
  }

  let sejamProfile;
  if (authMode === "mock") {
    await authHelpers.validateUserOtp(identifier, otp);
    await redisConnection.del(authHelpers.getOtpKey(identifier));
    sejamProfile = authHelpers.buildMockSejamProfile({
      identifier,
      isPrivatePerson,
    });
  } else {
    sejamProfile = await sejamServices.verifyOtpAndGetProfile(identifier, otp);
  }

  const wasNewUser = !user;
  user = await authHelpers.saveSejamUserAndInfo(identifier, sejamProfile, {
    preserveExistingRole: true,
  });

  if (wasNewUser) {
    await clubHelpers.handleNewUserClubRegistration({ user });
  }

  return models.user.findById(user._id).populate("role");
};

export const verifyAndBindTelegramUser = async ({
  telegramId,
  identifier,
  isPrivatePerson,
  otp,
  authMode,
}) => {
  const normalizedTelegramId = normalizeTelegramId(telegramId);
  const normalizedOtp = identityHelpers.normalizeIdentifier(otp);

  if (!/^\d{5}$/.test(normalizedOtp)) {
    const error = new Error("کد تایید باید ۵ رقم باشد.");
    error.status = 422;
    throw error;
  }

  ensureBotDevelopmentAccess(identifier);
  await Promise.all([
    enforceBotRateLimit({
      scope: "otp-verify-telegram",
      identifier: normalizedTelegramId,
      max: 10,
      windowSeconds: 15 * 60,
    }),
    enforceBotRateLimit({
      scope: "otp-verify-identifier",
      identifier,
      max: 10,
      windowSeconds: 15 * 60,
    }),
  ]);

  const user = await verifyAuthenticationOtp({
    identifier,
    isPrivatePerson,
    otp: normalizedOtp,
    authMode,
  });

  if (!user?._id) {
    const error = new Error("کاربر پس از احراز هویت یافت نشد.");
    error.status = 404;
    throw error;
  }

  const conflictingTelegramUser = await models.user.findOne({
    telegramId: normalizedTelegramId,
    _id: { $ne: user._id },
    isTelegramVerified: true,
  });
  if (conflictingTelegramUser) {
    const error = new Error(
      "این حساب تلگرام قبلاً به یک حساب کاربری دیگر متصل شده است.",
    );
    error.status = 409;
    throw error;
  }

  if (
    user.telegramId &&
    String(user.telegramId) !== normalizedTelegramId &&
    user.isTelegramVerified
  ) {
    const error = new Error(
      "این حساب کاربری قبلاً به حساب تلگرام دیگری متصل شده است.",
    );
    error.status = 409;
    throw error;
  }

  user.telegramId = normalizedTelegramId;
  user.isTelegramVerified = true;
  user.telegramVerifiedAt = new Date();
  await user.save();

  if (!authHelpers.isDevelopmentUsers(user)) {
    try {
      await smsServices.sendLoginNotifSms({
        mobile: user.mobile,
        fullName: user.fullName,
      });
    } catch (error) {
      console.error(
        "[telegram-bot] failed to send login notification:",
        error?.message || "unknown error",
      );
    }
  }

  return user;
};

export const getVerifiedTelegramUser = async (telegramId) => {
  const normalizedTelegramId = normalizeTelegramId(telegramId);
  const user = await models.user
    .findOne({
      telegramId: normalizedTelegramId,
      isTelegramVerified: true,
    })
    .populate("role");

  if (!user) {
    return null;
  }

  ensureBotDevelopmentAccess(identityHelpers.getUserIdentifier(user));
  return user;
};

export const unlinkTelegramUser = async (telegramId) => {
  const normalizedTelegramId = normalizeTelegramId(telegramId);
  return models.user.findOneAndUpdate(
    { telegramId: normalizedTelegramId },
    {
      $unset: { telegramId: 1 },
      $set: {
        isTelegramVerified: false,
        telegramVerifiedAt: null,
      },
    },
    { new: true },
  );
};
