import { Wallet, WalletTxn } from "@/db/models";
import type { WalletTxnType } from "@/db/types";
import { AppError } from "@/lib/errors";
import { createId } from "@/db/ids";
import { isWalletsMysql } from "@/db/activeDatabase";
import { isFinancialDualWriteEnabled, withFinancialDualWrite } from "@/db/dualWriteFinancial";
import { mutateWalletAndInsertTxnMysql, upsertWalletAndTxnMysql, upsertWalletMysql } from "@/db/mysql/financialWrites";
import { findWalletByUserIdMysql } from "@/db/mysql/wallets";

function roundMoney(value: number) {
  return Math.round(value * 100) / 100;
}

function mysqlWalletShape(row: { id: string; userId: string; balanceLyd: number }) {
  return {
    _id: row.id,
    id: row.id,
    userId: row.userId,
    balanceLyd: row.balanceLyd,
  } as any;
}

/** True when wallets are served from MySQL and mongoose is not the write primary. */
function walletsMysqlPrimary() {
  return isWalletsMysql() && !isFinancialDualWriteEnabled();
}

export async function ensureWallet(userId: string) {
  if (walletsMysqlPrimary()) {
    const existing = await findWalletByUserIdMysql(userId);
    if (existing) return mysqlWalletShape(existing);
    const id = createId();
    const now = new Date();
    await upsertWalletMysql({
      id,
      userId,
      balanceLyd: 0,
      createdAt: now,
      updatedAt: now,
    });
    return mysqlWalletShape({ id, userId, balanceLyd: 0 });
  }

  let wallet = await Wallet.findOne({ userId });
  if (!wallet) {
    wallet = await Wallet.create({ userId, balanceLyd: 0 });
    if (isFinancialDualWriteEnabled() || isWalletsMysql()) {
      try {
        await upsertWalletMysql({
          id: String(wallet._id),
          userId,
          balanceLyd: 0,
          createdAt: (wallet as any).createdAt,
          updatedAt: (wallet as any).updatedAt,
        });
      } catch (e) {
        console.error("[dual-write-financial] FAIL wallet.ensure:", e);
        await Wallet.deleteOne({ _id: wallet._id });
        throw e;
      }
    }
  } else if (isWalletsMysql()) {
    // Keep MySQL present for wallets that existed only in Mongo before cutover.
    const existing = await findWalletByUserIdMysql(userId);
    if (!existing && (isFinancialDualWriteEnabled() || isWalletsMysql())) {
      await upsertWalletMysql({
        id: String(wallet._id),
        userId,
        balanceLyd: roundMoney(wallet.balanceLyd),
        createdAt: (wallet as any).createdAt,
        updatedAt: (wallet as any).updatedAt,
      });
    }
  }
  return wallet;
}

export async function getWalletBalance(userId: string) {
  if (walletsMysqlPrimary()) {
    const wallet = await ensureWallet(userId);
    const row = await findWalletByUserIdMysql(userId);
    const balanceLyd = roundMoney(row?.balanceLyd ?? wallet.balanceLyd ?? 0);
    return {
      wallet: row ? mysqlWalletShape(row) : wallet,
      balanceLyd,
    };
  }

  const wallet = await ensureWallet(userId);
  if (isWalletsMysql()) {
    const row = await findWalletByUserIdMysql(userId);
    if (row) {
      return {
        wallet: mysqlWalletShape(row),
        balanceLyd: roundMoney(row.balanceLyd),
      };
    }
  }
  return { wallet, balanceLyd: roundMoney(wallet.balanceLyd) };
}

export async function creditWallet(params: {
  userId: string;
  amountLyd: number;
  type: WalletTxnType;
  bookingId?: string;
  topUpId?: string;
  note?: string;
  meta?: Record<string, unknown>;
}) {
  const amount = roundMoney(params.amountLyd);
  if (!(amount > 0)) throw new AppError(400, "Invalid credit amount");

  if (walletsMysqlPrimary()) {
    const wallet = await ensureWallet(params.userId);
    const walletId = String(wallet._id || wallet.id);
    const txnId = createId();
    const now = new Date();
    const result = await mutateWalletAndInsertTxnMysql({
      wallet: { id: walletId, userId: params.userId, createdAt: (wallet as any).createdAt },
      deltaLyd: amount,
      txn: {
        id: txnId,
        type: params.type,
        bookingId: params.bookingId,
        topUpId: params.topUpId,
        note: params.note,
        meta: params.meta,
        createdAt: now,
      },
    });
    return {
      wallet: mysqlWalletShape({ id: result.walletId, userId: params.userId, balanceLyd: result.balanceLyd }),
      txn: { _id: txnId, id: txnId } as any,
    };
  }

  return withFinancialDualWrite({
    site: "wallet.credit",
    mongoWrite: async () => {
      const wallet = await ensureWallet(params.userId);
      const prevBalance = roundMoney(wallet.balanceLyd);
      wallet.balanceLyd = roundMoney(prevBalance + amount);
      await wallet.save();

      const txn = await WalletTxn.create({
        walletId: wallet._id,
        userId: params.userId,
        type: params.type,
        amountLyd: amount,
        balanceAfter: wallet.balanceLyd,
        bookingId: params.bookingId,
        topUpId: params.topUpId,
        note: params.note,
        meta: params.meta,
      });

      return { wallet, txn, prevBalance };
    },
    mysqlWrite: async ({ wallet, txn }) => {
      await upsertWalletAndTxnMysql({
        wallet: {
          id: String(wallet._id),
          userId: params.userId,
          balanceLyd: roundMoney(wallet.balanceLyd),
          createdAt: (wallet as any).createdAt,
          updatedAt: (wallet as any).updatedAt,
        },
        txn: {
          id: String(txn._id),
          walletId: String(wallet._id),
          userId: params.userId,
          type: params.type,
          amountLyd: amount,
          balanceAfter: roundMoney(wallet.balanceLyd),
          bookingId: params.bookingId,
          topUpId: params.topUpId,
          note: params.note,
          meta: params.meta,
          createdAt: (txn as any).createdAt,
        },
      });
    },
    mongoCompensate: async ({ wallet, txn, prevBalance }) => {
      wallet.balanceLyd = prevBalance;
      await wallet.save();
      await WalletTxn.deleteOne({ _id: txn._id });
    },
  }).then(({ wallet, txn }) => ({ wallet, txn }));
}

export async function debitWallet(params: {
  userId: string;
  amountLyd: number;
  type: WalletTxnType;
  bookingId?: string;
  note?: string;
  meta?: Record<string, unknown>;
}) {
  const amount = roundMoney(params.amountLyd);
  if (!(amount > 0)) throw new AppError(400, "Invalid debit amount");

  if (walletsMysqlPrimary()) {
    const wallet = await ensureWallet(params.userId);
    const walletId = String(wallet._id || wallet.id);
    const txnId = createId();
    const now = new Date();
    let result;
    try {
      result = await mutateWalletAndInsertTxnMysql({
        wallet: { id: walletId, userId: params.userId, createdAt: (wallet as any).createdAt },
        deltaLyd: -amount,
        txn: { id: txnId, type: params.type, bookingId: params.bookingId, note: params.note, meta: params.meta, createdAt: now },
      });
    } catch (error) {
      if (error instanceof Error && error.message === "INSUFFICIENT_WALLET_BALANCE") {
        throw new AppError(400, "Insufficient wallet balance");
      }
      throw error;
    }
    return {
      wallet: mysqlWalletShape({ id: result.walletId, userId: params.userId, balanceLyd: result.balanceLyd }),
      txn: { _id: txnId, id: txnId } as any,
    };
  }

  return withFinancialDualWrite({
    site: "wallet.debit",
    mongoWrite: async () => {
      const wallet = await ensureWallet(params.userId);
      const prevBalance = roundMoney(wallet.balanceLyd);
      if (prevBalance + 1e-9 < amount) {
        throw new AppError(400, "Insufficient wallet balance");
      }
      wallet.balanceLyd = roundMoney(prevBalance - amount);
      await wallet.save();

      const txn = await WalletTxn.create({
        walletId: wallet._id,
        userId: params.userId,
        type: params.type,
        amountLyd: -amount,
        balanceAfter: wallet.balanceLyd,
        bookingId: params.bookingId,
        note: params.note,
        meta: params.meta,
      });

      return { wallet, txn, prevBalance };
    },
    mysqlWrite: async ({ wallet, txn }) => {
      await upsertWalletAndTxnMysql({
        wallet: {
          id: String(wallet._id),
          userId: params.userId,
          balanceLyd: roundMoney(wallet.balanceLyd),
          createdAt: (wallet as any).createdAt,
          updatedAt: (wallet as any).updatedAt,
        },
        txn: {
          id: String(txn._id),
          walletId: String(wallet._id),
          userId: params.userId,
          type: params.type,
          amountLyd: -amount,
          balanceAfter: roundMoney(wallet.balanceLyd),
          bookingId: params.bookingId,
          note: params.note,
          meta: params.meta,
          createdAt: (txn as any).createdAt,
        },
      });
    },
    mongoCompensate: async ({ wallet, txn, prevBalance }) => {
      wallet.balanceLyd = prevBalance;
      await wallet.save();
      await WalletTxn.deleteOne({ _id: txn._id });
    },
  }).then(({ wallet, txn }) => ({ wallet, txn }));
}
