import { Types } from "mongoose";
import FuelRequestModel from "../models/FuelRequestModel";
import UserModel from "../models/UserModel";
import WalletTransactionModel from "../models/WalletTransactionModel";
import { isConnected } from "./connectionService";
import { applyWalletDebit, reconcileWalletBalance } from "./walletService";
import { buildImageUrl } from "../utils/imageUrl";
import { PaginatedResult } from "./connectionService";
import { assertNotBlocked } from "./blockService";
import { notify } from "./notificationService";

/** Convert stored cents to dollar amount (2 decimal places). */
function centsToDollars(cents: number): number {
  return Math.round(cents) / 100;
}

/** Convert dollar amount to cents (rounds half-up). */
function dollarsToCents(dollars: number): number {
  return Math.round(dollars * 100);
}

export async function sendFuelRequest(
  senderId: string,
  receiverUserId: string,
  amount: number,
  message?: string,
): Promise<Record<string, unknown>> {
  if (senderId === receiverUserId) {
    throw new Error("CANNOT_SELF_REQUEST");
  }

  await assertNotBlocked(senderId, receiverUserId);

  const receiver = await UserModel.findOne({
    _id: new Types.ObjectId(receiverUserId),
    isDeleted: false,
    isBanned: false,
  })
    .select("_id fullName email image")
    .lean();

  if (!receiver) {
    throw new Error("RECEIVER_NOT_FOUND");
  }

  const connected = await isConnected(senderId, receiverUserId);
  if (!connected) {
    throw new Error("NOT_CONNECTED");
  }

  const amountCents = dollarsToCents(amount);

  const fuelRequest = await FuelRequestModel.create({
    senderId: new Types.ObjectId(senderId),
    receiverId: new Types.ObjectId(receiverUserId),
    amountCents,
    message: message ?? null,
    status: "pending",
  });

  const sender = await UserModel.findById(senderId)
    .select("fullName email")
    .lean();
  const senderName = (sender?.fullName || sender?.email || "Someone").trim();
  const amountLabel = `$${centsToDollars(amountCents).toFixed(2)}`;
  void notify({
    userId: receiverUserId,
    type: "fuel",
    eventType: "request_received",
    title: "Fuel payment request",
    body: `${senderName} requested ${amountLabel} for fuel`,
    data: {
      fuelRequestId: String(fuelRequest._id),
      senderId,
      receiverId: receiverUserId,
      amountCents,
    },
  });

  return {
    _id: fuelRequest._id,
    receiver: {
      _id: receiver._id,
      fullName: receiver.fullName,
      email: receiver.email,
      image: receiver.image ? buildImageUrl(receiver.image) : null,
    },
    amount: centsToDollars(fuelRequest.amountCents),
    amountCents: fuelRequest.amountCents,
    message: fuelRequest.message,
    status: fuelRequest.status,
    createdAt: fuelRequest.createdAt,
  };
}

export async function getFuelRequests(
  userId: string,
  direction: "sent" | "received",
  page: number,
  limit: number,
  status?: "pending" | "accepted" | "rejected",
): Promise<PaginatedResult<Record<string, unknown>>> {
  const skip = (page - 1) * limit;
  const uid = new Types.ObjectId(userId);

  const query: Record<string, unknown> = {
    [direction === "sent" ? "senderId" : "receiverId"]: uid,
  };
  if (status) {
    query.status = status;
  }

  const total = await FuelRequestModel.countDocuments(query);
  const requests = await FuelRequestModel.find(query)
    .sort({ createdAt: -1 })
    .skip(skip)
    .limit(limit)
    .lean();

  if (requests.length === 0) {
    return { data: [], total, page, totalPages: Math.ceil(total / limit) };
  }

  const otherIds = requests.map((r) =>
    direction === "sent" ? r.receiverId : r.senderId,
  );

  const users = await UserModel.find({
    _id: { $in: otherIds },
    isDeleted: false,
  })
    .select("_id fullName email image")
    .lean();

  const userMap = new Map(users.map((u) => [String(u._id), u]));

  const data = requests.map((r) => {
    const otherId = String(direction === "sent" ? r.receiverId : r.senderId);
    const user = userMap.get(otherId);
    return {
      _id: r._id,
      [direction === "sent" ? "receiver" : "sender"]: user
        ? {
            _id: user._id,
            fullName: user.fullName,
            email: user.email,
            image: user.image ? buildImageUrl(user.image) : null,
          }
        : null,
      amount: centsToDollars(r.amountCents),
      amountCents: r.amountCents,
      message: r.message,
      status: r.status,
      createdAt: r.createdAt,
    };
  });

  return { data, total, page, totalPages: Math.ceil(total / limit) };
}

export async function transferFuel(
  requestId: string,
  payerId: string,
): Promise<{ walletBalance: number; walletBalanceCents: number; request: Record<string, unknown> }> {
  const request = await FuelRequestModel.findById(requestId);
  if (!request) throw new Error("NOT_FOUND");
  if (String(request.receiverId) !== payerId) throw new Error("FORBIDDEN");
  if (request.status !== "pending") throw new Error("ALREADY_PROCESSED");

  const debitResult = await applyWalletDebit({
    userId: payerId,
    amountCents: request.amountCents,
    type: "payment_debit",
    description: `Fuel payment to connection — request ${requestId}`,
    externalRef: `fuel_debit_${requestId}`,
    metadata: { fuelRequestId: requestId, recipientId: String(request.senderId) },
  });

  if (!debitResult.ok) {
    throw new Error("INSUFFICIENT_FUNDS");
  }

  const senderId = String(request.senderId);

  const creditedUser = await UserModel.findByIdAndUpdate(
    senderId,
    { $inc: { walletBalanceCents: request.amountCents } },
    { new: true },
  );

  const balanceAfterCredit = creditedUser?.walletBalanceCents ?? 0;

  await WalletTransactionModel.create({
    userId: new Types.ObjectId(senderId),
    deltaCents: request.amountCents,
    balanceAfterCents: balanceAfterCredit,
    currency: "usd",
    type: "adjustment",
    status: "completed",
    description: `Fuel payment received from connection — request ${requestId}`,
    externalRef: `fuel_credit_${requestId}`,
    metadata: { fuelRequestId: requestId, payerId },
  });

  await reconcileWalletBalance(senderId);

  request.status = "accepted";
  await request.save();

  const payerBalance = debitResult.balanceAfterCents;

  const payer = await UserModel.findById(payerId).select("fullName email").lean();
  const payerName = (payer?.fullName || payer?.email || "Someone").trim();
  const amountLabel = `$${centsToDollars(request.amountCents).toFixed(2)}`;
  void notify({
    userId: senderId,
    type: "fuel",
    eventType: "transferred",
    title: "Fuel payment received",
    body: `${payerName} sent you ${amountLabel}`,
    data: {
      fuelRequestId: requestId,
      senderId,
      receiverId: payerId,
      amountCents: request.amountCents,
    },
    dedupeKey: `fuel:transferred:${requestId}`,
  });

  return {
    walletBalance: centsToDollars(payerBalance),
    walletBalanceCents: payerBalance,
    request: {
      _id: request._id,
      senderId: request.senderId,
      receiverId: request.receiverId,
      amount: centsToDollars(request.amountCents),
      amountCents: request.amountCents,
      message: request.message,
      status: request.status,
      updatedAt: request.updatedAt,
    },
  };
}

export async function rejectFuelRequest(
  requestId: string,
  userId: string,
): Promise<Record<string, unknown>> {
  const request = await FuelRequestModel.findById(requestId);
  if (!request) throw new Error("NOT_FOUND");
  if (String(request.receiverId) !== userId) throw new Error("FORBIDDEN");
  if (request.status !== "pending") throw new Error("ALREADY_PROCESSED");

  request.status = "rejected";
  await request.save();

  return {
    _id: request._id,
    senderId: request.senderId,
    receiverId: request.receiverId,
    amount: centsToDollars(request.amountCents),
    amountCents: request.amountCents,
    message: request.message,
    status: request.status,
    updatedAt: request.updatedAt,
  };
}
