import { Markup, Telegraf } from "telegraf";

import {
  getVerifiedTelegramUser,
  requestTelegramAuthenticationOtp,
  unlinkTelegramUser,
  verifyAndBindTelegramUser,
} from "./auth.service.js";
import { telegramBotConfig } from "./config.js";
import {
  createIssuanceQuote,
  createRedemptionQuote,
  getLatestTelegramRequests,
  getTelegramFund,
  getTelegramFunds,
  getTelegramRedeemableUnits,
  getTelegramRequestStatus,
  startTelegramIssuance,
  submitTelegramRedemption,
} from "./investment.service.js";
import {
  clearBotState,
  createShortId,
  getBotState,
  markUpdateHandled,
  setBotState,
} from "./state.js";
import { isSepahBrokerFund } from "$services/sepah/sepah-broker.helper.js";

let botInstance;

const normalizeDigits = (value = "") =>
  String(value)
    .replace(/[۰-۹]/g, (digit) => String("۰۱۲۳۴۵۶۷۸۹".indexOf(digit)))
    .replace(/[٠-٩]/g, (digit) => String("٠١٢٣٤٥٦٧٨٩".indexOf(digit)));

const parseIntegerInput = (value = "") => {
  const normalized = normalizeDigits(value).replace(/[,\s٬]/g, "");
  return /^\d+$/.test(normalized) ? Number(normalized) : Number.NaN;
};

const formatNumber = (value) =>
  new Intl.NumberFormat("fa-IR").format(Number(value || 0));

const operationLabels = (fundCode) =>
  isSepahBrokerFund(fundCode)
    ? { issuance: "خرید", redemption: "فروش" }
    : { issuance: "صدور", redemption: "ابطال" };

const statusLabels = {
  wait_for_payment: "در انتظار پرداخت",
  reviewing: "در حال بررسی",
  approved: "تاییدشده",
  rejected: "ردشده",
  failed: "ناموفق",
};

const mainMenuKeyboard = () =>
  Markup.inlineKeyboard([
    [Markup.button.callback("صندوق‌ها", "menu:funds")],
    [Markup.button.callback("درخواست‌های من", "menu:requests")],
    [Markup.button.callback("قطع اتصال حساب", "auth:logout")],
  ]);

const backToMenuKeyboard = () =>
  Markup.inlineKeyboard([
    [Markup.button.callback("بازگشت به منوی اصلی", "menu:home")],
  ]);

const requirePrivateChat = async (ctx, next) => {
  if (ctx.chat?.type !== "private") {
    await ctx.reply("عملیات مالی ربات فقط در گفت‌وگوی خصوصی قابل استفاده است.");
    return;
  }
  await next();
};

const ignoreDuplicateUpdate = async (ctx, next) => {
  const isFirstDelivery = await markUpdateHandled(ctx.update?.update_id);
  if (!isFirstDelivery) {
    return;
  }
  await next();
};

const safelyDeleteIncomingMessage = async (ctx) => {
  try {
    await ctx.deleteMessage();
  } catch {
    // Message deletion is best-effort and depends on Telegram permissions.
  }
};

const settleWithin = async (promise, timeoutMs) => {
  let timeoutId;
  const timeoutPromise = new Promise((_, reject) => {
    timeoutId = setTimeout(
      () => reject(new Error("Telegram callback acknowledgement timed out.")),
      timeoutMs,
    );
  });

  try {
    return await Promise.race([promise, timeoutPromise]);
  } finally {
    clearTimeout(timeoutId);
  }
};

const replyMainMenu = async (ctx, user) => {
  const name = String(user?.fullName || "").trim();
  await ctx.reply(
    `${name ? `${name} عزیز، ` : ""}یکی از گزینه‌های زیر را انتخاب کنید:`,
    mainMenuKeyboard(),
  );
};

const beginAuthentication = async (ctx) => {
  const telegramId = String(ctx.from.id);
  await clearBotState(telegramId);
  await setBotState(telegramId, { step: "awaiting_identifier" });
  await ctx.reply(
    "کد ملی ۱۰ رقمی یا شناسه ملی ۱۱ رقمی را ارسال کنید.\nکد تایید فقط به شماره موبایل ثبت‌شده کاربر ارسال می‌شود.",
  );
};

const authenticateOrMenu = async (ctx) => {
  const user = await getVerifiedTelegramUser(ctx.from.id);
  if (user) {
    await clearBotState(ctx.from.id);
    await replyMainMenu(ctx, user);
    return;
  }
  await beginAuthentication(ctx);
};

const showFunds = async (ctx) => {
  const funds = await getTelegramFunds();
  if (funds.length === 0) {
    await ctx.reply(
      "در حال حاضر صندوقی برای عملیات از طریق ربات فعال نیست.",
      backToMenuKeyboard(),
    );
    return;
  }

  const rows = funds.map((fund) => [
    Markup.button.callback(
      String(fund.fundName || `صندوق ${fund.fundRegisterCode}`).trim(),
      `fund:${fund.fundRegisterCode}`,
    ),
  ]);
  rows.push([Markup.button.callback("بازگشت", "menu:home")]);
  await ctx.reply("صندوق موردنظر را انتخاب کنید:", Markup.inlineKeyboard(rows));
};

const showFund = async (ctx, fundCode) => {
  const fund = await getTelegramFund(fundCode);
  if (!fund) {
    await ctx.reply("صندوق انتخاب‌شده در دسترس نیست.");
    return;
  }

  const labels = operationLabels(fundCode);
  const subscriptionNav = Number(fund?.nav?.subscriptionNav || 0);
  const redemptionNav = Number(fund?.nav?.cancelNav || 0);
  await ctx.reply(
    [
      String(fund.fundName || `صندوق ${fundCode}`).trim(),
      `NAV ${labels.issuance}: ${formatNumber(subscriptionNav)} ریال`,
      `NAV ${labels.redemption}: ${formatNumber(redemptionNav)} ریال`,
    ].join("\n"),
    Markup.inlineKeyboard([
      [
        Markup.button.callback(labels.issuance, `issue:${fundCode}`),
        Markup.button.callback(labels.redemption, `redeem:${fundCode}`),
      ],
      [Markup.button.callback("بازگشت به صندوق‌ها", "menu:funds")],
    ]),
  );
};

const quoteText = (quote) => {
  const labels = operationLabels(quote.fundCode);
  const operationLabel = labels[quote.operation];
  const rows = [
    `${operationLabel} ${quote.fundName}`,
    `NAV: ${formatNumber(quote.unitPrice)} ریال`,
    `تعداد واحد: ${formatNumber(quote.units)}`,
    `مبلغ: ${formatNumber(quote.grossAmountRial)} ریال`,
    `کارمزد: ${formatNumber(quote.wageAmountRial)} ریال`,
  ];

  rows.push(
    quote.operation === "issuance"
      ? `مبلغ قابل پرداخت: ${formatNumber(quote.amountRial)} ریال`
      : `مبلغ تقریبی قابل دریافت: ${formatNumber(quote.amountRial)} ریال`,
  );
  rows.push("NAV و مبلغ نهایی هنگام ثبت دوباره بررسی می‌شود.");
  return rows.join("\n");
};

const storeAndShowQuote = async (ctx, quote, quoteId = createShortId()) => {
  await setBotState(ctx.from.id, {
    step: "awaiting_quote_confirmation",
    quoteId,
    quote,
  });

  const action =
    quote.operation === "issuance"
      ? `confirm:issue:${quoteId}`
      : `confirm:redeem:${quoteId}`;
  await ctx.reply(
    quoteText(quote),
    Markup.inlineKeyboard([
      [Markup.button.callback("تایید نهایی", action)],
      [Markup.button.callback("لغو", `quote:cancel:${quoteId}`)],
    ]),
  );
};

const showRequestStatus = async (ctx, request) => {
  const labels = operationLabels(
    request?.meta?.fundRegisterCode ||
      request?.meta?.sepahBroker?.fundRegisterCode ||
      request?.fundSeoRegisterNo,
  );
  const operationLabel =
    request.operationType === "redemption"
      ? labels.redemption
      : labels.issuance;

  await ctx.reply(
    [
      `${operationLabel} ${request.fundName || ""}`.trim(),
      `کد پیگیری: ${request.trackId || request._id}`,
      `وضعیت: ${statusLabels[request.status] || request.status}`,
      request.units > 0 ? `تعداد واحد: ${formatNumber(request.units)}` : "",
      request.amountRial > 0
        ? `مبلغ: ${formatNumber(request.amountRial)} ریال`
        : "",
    ]
      .filter(Boolean)
      .join("\n"),
    Markup.inlineKeyboard([
      [
        Markup.button.callback(
          "بررسی مجدد وضعیت",
          `status:${String(request._id)}`,
        ),
      ],
      [Markup.button.callback("منوی اصلی", "menu:home")],
    ]),
  );
};

const showLatestRequests = async (ctx, user) => {
  const requests = await getLatestTelegramRequests({ user });
  if (requests.length === 0) {
    await ctx.reply("هنوز درخواستی ثبت نکرده‌اید.", backToMenuKeyboard());
    return;
  }

  const rows = requests.map((request) => [
    Markup.button.callback(
      `${request.fundName || "صندوق"} — ${
        statusLabels[request.status] || request.status
      }`,
      `status:${String(request._id)}`,
    ),
  ]);
  rows.push([Markup.button.callback("بازگشت", "menu:home")]);
  await ctx.reply("آخرین درخواست‌ها:", Markup.inlineKeyboard(rows));
};

const handleAuthenticationText = async (ctx, state) => {
  const telegramId = String(ctx.from.id);

  if (state.step === "awaiting_identifier") {
    const identity = await requestTelegramAuthenticationOtp({
      telegramId,
      rawIdentifier: ctx.message.text,
    });
    await safelyDeleteIncomingMessage(ctx);
    await setBotState(telegramId, {
      step: "awaiting_otp",
      ...identity,
    });
    await ctx.reply(
      "کد تایید ۵ رقمی ارسال شد. کد را وارد کنید.",
      Markup.inlineKeyboard([
        [Markup.button.callback("ارسال مجدد کد", "auth:resend")],
        [Markup.button.callback("تغییر کد ملی", "auth:restart")],
      ]),
    );
    return true;
  }

  if (state.step === "awaiting_otp") {
    const user = await verifyAndBindTelegramUser({
      telegramId,
      identifier: state.identifier,
      isPrivatePerson: state.isPrivatePerson,
      otp: ctx.message.text,
      authMode: state.authMode,
    });
    await safelyDeleteIncomingMessage(ctx);
    await clearBotState(telegramId);
    await ctx.reply("احراز هویت تلگرام با موفقیت انجام شد.");
    await replyMainMenu(ctx, user);
    return true;
  }

  return false;
};

const handleInvestmentText = async (ctx, state, user) => {
  if (state.step === "awaiting_issuance_amount") {
    const amount = parseIntegerInput(ctx.message.text);
    if (!Number.isSafeInteger(amount) || amount <= 0) {
      throw new Error("مبلغ را به‌صورت عدد صحیح و به ریال وارد کنید.");
    }

    const quote = await createIssuanceQuote({
      user,
      fundCode: state.fundCode,
      investmentAmountRial: amount,
    });
    await storeAndShowQuote(ctx, quote);
    return true;
  }

  if (state.step === "awaiting_redemption_units") {
    const units = parseIntegerInput(ctx.message.text);
    if (!Number.isSafeInteger(units) || units <= 0) {
      throw new Error("تعداد واحد را به‌صورت عدد صحیح وارد کنید.");
    }

    const quote = await createRedemptionQuote({
      user,
      fundCode: state.fundCode,
      units,
    });
    await storeAndShowQuote(ctx, quote);
    return true;
  }

  return false;
};

const errorMessage = (error) =>
  error?.response?.data?.message ||
  error?.message ||
  "عملیات انجام نشد. لطفاً دوباره تلاش کنید.";

const replyBotError = async (ctx, error) => {
  const buttons = [];
  if (error?.registerUrl) {
    buttons.push([
      Markup.button.url("ثبت‌نام کارگزاری سپه", error.registerUrl),
    ]);
  }
  buttons.push([Markup.button.callback("منوی اصلی", "menu:home")]);
  await ctx.reply(errorMessage(error), Markup.inlineKeyboard(buttons));
};

const withAction = (handler) => async (ctx) => {
  const action = String(ctx.callbackQuery?.data || "unknown");
  const startedAt = Date.now();
  console.log("[telegram-bot] action received:", action);

  try {
    try {
      await settleWithin(ctx.answerCbQuery(), 3_000);
    } catch (error) {
      console.warn("[telegram-bot] callback acknowledgement failed:", {
        action,
        message: error?.message || "unknown error",
      });
    }

    await handler(ctx);
    console.log("[telegram-bot] action completed:", {
      action,
      durationMs: Date.now() - startedAt,
    });
  } catch (error) {
    console.error("[telegram-bot] action failed:", {
      action,
      durationMs: Date.now() - startedAt,
      code: error?.code || "",
      message: error?.message || "unknown error",
    });
    await replyBotError(ctx, error);
  }
};

const registerHandlers = (bot) => {
  bot.use(requirePrivateChat);
  bot.use(ignoreDuplicateUpdate);

  bot.start(async (ctx) => {
    try {
      await authenticateOrMenu(ctx);
    } catch (error) {
      await ctx.reply(errorMessage(error));
    }
  });

  bot.command("menu", async (ctx) => {
    try {
      await authenticateOrMenu(ctx);
    } catch (error) {
      await ctx.reply(errorMessage(error));
    }
  });

  bot.command("cancel", async (ctx) => {
    await clearBotState(ctx.from.id);
    const user = await getVerifiedTelegramUser(ctx.from.id);
    if (user) {
      await replyMainMenu(ctx, user);
    } else {
      await beginAuthentication(ctx);
    }
  });

  bot.action(
    "menu:home",
    withAction(async (ctx) => {
      const user = await getVerifiedTelegramUser(ctx.from.id);
      if (!user) {
        await beginAuthentication(ctx);
        return;
      }
      await clearBotState(ctx.from.id);
      await replyMainMenu(ctx, user);
    }),
  );

  bot.action(
    "menu:funds",
    withAction(async (ctx) => {
      const user = await getVerifiedTelegramUser(ctx.from.id);
      if (!user) {
        await beginAuthentication(ctx);
        return;
      }
      await showFunds(ctx);
    }),
  );

  bot.action(
    "menu:requests",
    withAction(async (ctx) => {
      const user = await getVerifiedTelegramUser(ctx.from.id);
      if (!user) {
        await beginAuthentication(ctx);
        return;
      }
      await showLatestRequests(ctx, user);
    }),
  );

  bot.action(
    "auth:restart",
    withAction(async (ctx) => beginAuthentication(ctx)),
  );

  bot.action(
    "auth:resend",
    withAction(async (ctx) => {
      const state = await getBotState(ctx.from.id);
      if (state?.step !== "awaiting_otp" || !state.identifier) {
        await beginAuthentication(ctx);
        return;
      }

      const identity = await requestTelegramAuthenticationOtp({
        telegramId: ctx.from.id,
        rawIdentifier: state.identifier,
      });
      await setBotState(ctx.from.id, {
        step: "awaiting_otp",
        ...identity,
      });
      await ctx.reply("کد تایید جدید ارسال شد.");
    }),
  );

  bot.action(
    "auth:logout",
    withAction(async (ctx) => {
      await unlinkTelegramUser(ctx.from.id);
      await clearBotState(ctx.from.id);
      await ctx.reply("اتصال حساب تلگرام قطع شد.");
      await beginAuthentication(ctx);
    }),
  );

  bot.action(
    /^fund:(\d+)$/,
    withAction(async (ctx) => showFund(ctx, ctx.match[1])),
  );

  bot.action(
    /^issue:(\d+)$/,
    withAction(async (ctx) => {
      const user = await getVerifiedTelegramUser(ctx.from.id);
      if (!user) {
        await beginAuthentication(ctx);
        return;
      }
      const fund = await getTelegramFund(ctx.match[1]);
      if (!fund) {
        throw new Error("صندوق انتخاب‌شده در دسترس نیست.");
      }
      await setBotState(ctx.from.id, {
        step: "awaiting_issuance_amount",
        fundCode: ctx.match[1],
      });
      await ctx.reply(
        "مبلغ سرمایه‌گذاری را به ریال و بدون کارمزد وارد کنید.\nکارمزد و مبلغ قابل پرداخت در مرحله بعد نمایش داده می‌شود.",
      );
    }),
  );

  bot.action(
    /^redeem:(\d+)$/,
    withAction(async (ctx) => {
      const user = await getVerifiedTelegramUser(ctx.from.id);
      if (!user) {
        await beginAuthentication(ctx);
        return;
      }
      const fund = await getTelegramFund(ctx.match[1]);
      if (!fund) {
        throw new Error("صندوق انتخاب‌شده در دسترس نیست.");
      }
      const redeemableUnits = await getTelegramRedeemableUnits({
        user,
        fundCode: ctx.match[1],
      });
      await setBotState(ctx.from.id, {
        step: "awaiting_redemption_units",
        fundCode: ctx.match[1],
      });
      await ctx.reply(
        `تعداد واحد قابل ابطال/فروش: ${formatNumber(redeemableUnits)}\nتعداد موردنظر را وارد کنید.`,
      );
    }),
  );

  bot.action(
    /^quote:cancel:([a-f0-9]+)$/,
    withAction(async (ctx) => {
      const state = await getBotState(ctx.from.id);
      if (state?.quoteId === ctx.match[1]) {
        await clearBotState(ctx.from.id);
      }
      const user = await getVerifiedTelegramUser(ctx.from.id);
      await ctx.reply("عملیات لغو شد.");
      if (user) {
        await replyMainMenu(ctx, user);
      }
    }),
  );

  bot.action(
    /^confirm:issue:([a-f0-9]+)$/,
    withAction(async (ctx) => {
      const user = await getVerifiedTelegramUser(ctx.from.id);
      const state = await getBotState(ctx.from.id);
      if (
        !user ||
        state?.step !== "awaiting_quote_confirmation" ||
        state.quoteId !== ctx.match[1] ||
        state.quote?.operation !== "issuance"
      ) {
        throw new Error("پیش‌نمایش معتبر نیست. عملیات را دوباره شروع کنید.");
      }

      try {
        const result = await startTelegramIssuance({
          user,
          telegramId: ctx.from.id,
          quoteId: state.quoteId,
          quote: state.quote,
        });
        await clearBotState(ctx.from.id);
        await ctx.reply(
          "درخواست ایجاد شد. برای تکمیل خرید وارد درگاه پرداخت شوید.",
          Markup.inlineKeyboard([
            [Markup.button.url("ورود به درگاه پرداخت", result.redirectUrl)],
            [
              Markup.button.callback(
                "بررسی وضعیت پرداخت",
                `status:${result.requestId}`,
              ),
            ],
          ]),
        );
      } catch (error) {
        if (error?.code === "QUOTE_CHANGED" && error.quote) {
          await storeAndShowQuote(ctx, error.quote, state.quoteId);
          return;
        }
        throw error;
      }
    }),
  );

  bot.action(
    /^confirm:redeem:([a-f0-9]+)$/,
    withAction(async (ctx) => {
      const user = await getVerifiedTelegramUser(ctx.from.id);
      const state = await getBotState(ctx.from.id);
      if (
        !user ||
        state?.step !== "awaiting_quote_confirmation" ||
        state.quoteId !== ctx.match[1] ||
        state.quote?.operation !== "redemption"
      ) {
        throw new Error("پیش‌نمایش معتبر نیست. عملیات را دوباره شروع کنید.");
      }

      try {
        const result = await submitTelegramRedemption({
          user,
          telegramId: ctx.from.id,
          quoteId: state.quoteId,
          quote: state.quote,
        });
        await clearBotState(ctx.from.id);
        await ctx.reply(
          result.message || "درخواست ابطال/فروش با موفقیت ثبت شد.",
          Markup.inlineKeyboard([
            [
              Markup.button.callback(
                "مشاهده وضعیت",
                `status:${String(result.data?._id || "")}`,
              ),
            ],
            [Markup.button.callback("منوی اصلی", "menu:home")],
          ]),
        );
      } catch (error) {
        if (error?.code === "QUOTE_CHANGED" && error.quote) {
          await storeAndShowQuote(ctx, error.quote, state.quoteId);
          return;
        }
        throw error;
      }
    }),
  );

  bot.action(
    /^status:([a-f0-9]{24})$/,
    withAction(async (ctx) => {
      const user = await getVerifiedTelegramUser(ctx.from.id);
      if (!user) {
        await beginAuthentication(ctx);
        return;
      }
      const request = await getTelegramRequestStatus({
        user,
        requestId: ctx.match[1],
      });
      await showRequestStatus(ctx, request);
    }),
  );

  bot.on("text", async (ctx) => {
    try {
      const state = await getBotState(ctx.from.id);
      const user = await getVerifiedTelegramUser(ctx.from.id);

      if (!user) {
        if (await handleAuthenticationText(ctx, state || {})) {
          return;
        }
        await beginAuthentication(ctx);
        return;
      }

      if (await handleInvestmentText(ctx, state || {}, user)) {
        return;
      }

      await replyMainMenu(ctx, user);
    } catch (error) {
      console.error(
        "[telegram-bot] text handler failed:",
        error?.message || "unknown error",
      );
      await replyBotError(ctx, error);
    }
  });

  bot.catch(async (error, ctx) => {
    console.error(
      "[telegram-bot] unhandled update error:",
      error?.message || "unknown error",
    );
    try {
      await ctx.reply("خطای موقت رخ داد. لطفاً دوباره تلاش کنید.");
    } catch {
      // Ignore a second Telegram delivery error.
    }
  });
};

export const getTelegramBot = () => {
  if (!botInstance) {
    botInstance = new Telegraf(telegramBotConfig.token);
    registerHandlers(botInstance);
  }
  return botInstance;
};
