import type { ExchangeClient } from "../exchange/client.js";
import type {
  ChatMsg,
  ListOrdersParams,
  MarketAd,
  MarketSearchParams,
  MyAd,
  OrderRecord,
  TradeType,
} from "../exchange/types.js";
import {
  BybitP2PApi,
  type BybitAdItem,
  type BybitChatMessage,
  type BybitOrderItem,
  type BybitSide,
} from "./p2p-api.js";
import type { BybitClient } from "./client.js";

/**
 * Adapter Bybit: traduz a P2P Open API para a interface neutra
 * `ExchangeClient`.
 *
 * Mapeamento de lado (CONFIRMAR contra a conta real): assumimos
 *   side "0" = BUY, "1" = SELL (ponto de vista do anunciante).
 * A direção do chat depende do `userId` próprio (`selfUserId`), obtido do
 * cadastro da conta quando disponível.
 */
export class BybitExchangeClient implements ExchangeClient {
  readonly exchange = "bybit" as const;
  private readonly api: BybitP2PApi;

  constructor(
    readonly accountId: number,
    client: BybitClient,
    private readonly selfUserId: string | null = null,
  ) {
    this.api = new BybitP2PApi(client);
  }

  async searchMarketAds(params: MarketSearchParams): Promise<MarketAd[]> {
    const res = await this.api.onlineAds({
      tokenId: params.asset,
      currencyId: params.fiat,
      side: tradeTypeToSide(params.tradeType),
      page: String(params.page ?? 1),
      size: String(params.rows ?? 20),
      payment: params.payTypes,
      amount: params.transAmount,
    });
    return (res.items ?? []).map((a) => marketAdFromItem(a, params));
  }

  async listMyAds(): Promise<MyAd[]> {
    const res = await this.api.myAds({ page: "1", size: "100" });
    return (res.items ?? []).map(myAdFromItem);
  }

  async updateAdPrice(advNo: string, price: string): Promise<void> {
    // A Bybit costuma exigir o payload completo no update; enviamos o mínimo
    // conhecido (id + preço). Confirmar os campos obrigatórios na doc real.
    await this.api.updateAd({ id: advNo, priceType: "0", premium: "", price });
  }

  async listOrders(params?: ListOrdersParams): Promise<OrderRecord[]> {
    const res = params?.pendingOnly
      ? await this.api.pendingOrders({ page: "1", size: String(params?.rows ?? 50) })
      : await this.api.orders({ page: "1", size: String(params?.rows ?? 50) });
    return (res.items ?? []).map(orderFromItem);
  }

  async getChatMessages(orderNo: string): Promise<ChatMsg[]> {
    const res = await this.api.chatMessages({ orderId: orderNo, page: "1", size: "100" });
    return (res.items ?? []).map((m) => chatFromMessage(m, this.selfUserId));
  }

  async sendChatMessage(orderNo: string, text: string): Promise<void> {
    await this.api.sendMessage({
      orderId: orderNo,
      message: text,
      contentType: "str",
      msgUuid: cryptoUuid(),
    });
  }
}

/* ---- mapeadores ---- */

function tradeTypeToSide(t: TradeType): BybitSide {
  return t === "BUY" ? "0" : "1";
}
function sideToTradeType(side: number | undefined): TradeType {
  return side === 1 ? "SELL" : "BUY";
}

function marketAdFromItem(a: BybitAdItem, params: MarketSearchParams): MarketAd {
  return {
    advNo: a.id,
    advertiserName: a.nickName ?? null,
    advertiserId: a.userId ?? a.accountId ?? null,
    price: a.price,
    asset: a.tokenId ?? params.asset,
    fiat: a.currencyId ?? params.fiat,
    tradeType: params.tradeType,
    minAmount: a.minAmount ?? null,
    maxAmount: a.maxAmount ?? null,
    availableAmount: a.lastQuantity ?? a.quantity ?? null,
    payTypes: a.payments ?? [],
    finishRate: a.recentExecuteRate ?? null,
    monthOrderCount: a.finishNum ?? null,
    raw: a,
  };
}

function myAdFromItem(a: BybitAdItem): MyAd {
  return {
    advNo: a.id,
    asset: a.tokenId,
    fiat: a.currencyId,
    tradeType: sideToTradeType(a.side),
    price: a.price,
    priceType: null,
    status: typeof a.status === "number" ? a.status : null,
    surplusAmount: a.lastQuantity ?? a.quantity ?? null,
    minAmount: a.minAmount ?? null,
    maxAmount: a.maxAmount ?? null,
    raw: a,
  };
}

function orderFromItem(o: BybitOrderItem): OrderRecord {
  return {
    orderNo: o.id,
    advNo: o.itemId ?? null,
    tradeType: sideToTradeType(o.side),
    asset: o.tokenId,
    fiat: o.currencyId,
    amount: o.quantity ?? null,
    totalPrice: o.amount ?? null,
    status: typeof o.status === "number" ? o.status : null,
    statusText: null,
    counterpartyRef: o.targetUserId ?? null,
    counterpartyName: o.targetNickName ?? o.buyerRealName ?? o.sellerRealName ?? null,
    // Status de "pago" da Bybit (confirmar o código): paid costuma ser 20.
    isPaid: o.status === 20,
    createTime: o.createDate ? Number(o.createDate) : null,
    raw: o,
  };
}

function chatFromMessage(m: BybitChatMessage, selfUserId: string | null): ChatMsg {
  const id = m.msgUuid ?? m.id ?? String(m.createDate ?? Date.now());
  return {
    id: String(id),
    orderNo: m.orderId ?? "",
    direction: selfUserId && m.userId === selfUserId ? "out" : "in",
    type: typeof m.contentType === "string" ? m.contentType : "text",
    content: m.message ?? "",
    createTime: m.createDate ? Number(m.createDate) : Date.now(),
    raw: m,
  };
}

function cryptoUuid(): string {
  return globalThis.crypto?.randomUUID?.() ?? String(Date.now());
}
