import type { ExchangeClient } from "../exchange/client.js";
import type {
  ChatMsg,
  ListOrdersParams,
  MarketAd,
  MarketSearchParams,
  MyAd,
  OrderRecord,
  TradeType,
} from "../exchange/types.js";
import { C2CApi, type AdDetail, type ChatMessage, type MarketAdRaw, type OrderDetail } from "./c2c-api.js";
import type { BinanceC2CClient } from "./client.js";

/**
 * Adapter Binance: traduz a C2CApi (tipos crus da Binance) para a interface
 * neutra `ExchangeClient`. A lógica de negócio só vê os tipos de
 * `src/exchange/types.ts`.
 *
 * Códigos de status (BUY/SELL, números) vêm da Binance — `trade_type` aqui é
 * sempre do ponto de vista do anunciante, como na convenção do projeto.
 */
export class BinanceExchangeClient implements ExchangeClient {
  readonly exchange = "binance" as const;
  private readonly api: C2CApi;

  constructor(
    readonly accountId: number,
    client: BinanceC2CClient,
  ) {
    this.api = new C2CApi(client);
  }

  async searchMarketAds(params: MarketSearchParams): Promise<MarketAd[]> {
    const res = await this.api.searchAds({
      asset: params.asset,
      fiat: params.fiat,
      tradeType: params.tradeType,
      page: params.page ?? 1,
      rows: params.rows ?? 20,
      payTypes: params.payTypes,
      transAmount: params.transAmount,
    });
    return (res.data ?? []).map((a) => marketAdFromRaw(a, params.tradeType));
  }

  async listMyAds(): Promise<MyAd[]> {
    const res = await this.api.listAds({ page: 1, rows: 100 });
    return (res.data ?? []).map(myAdFromDetail);
  }

  async updateAdPrice(advNo: string, price: string): Promise<void> {
    await this.api.updateAd({ advNo, price: Number(price) });
  }

  async listOrders(params?: ListOrdersParams): Promise<OrderRecord[]> {
    const res = await this.api.listOrders({
      page: params?.page ?? 1,
      rows: params?.rows ?? 50,
    });
    return (res.data ?? []).map(orderFromDetail);
  }

  async getChatMessages(orderNo: string): Promise<ChatMsg[]> {
    const res = await this.api.getChatMessages({ orderNo, page: 1, rows: 100, sort: "asc" });
    return (res.data ?? []).map(chatFromMessage);
  }

  async sendChatMessage(orderNo: string, text: string): Promise<void> {
    await this.api.sendTextMessage({ orderNo, message: text, uuid: cryptoUuid() });
  }
}

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

function marketAdFromRaw(a: MarketAdRaw, tradeType: TradeType): MarketAd {
  return {
    advNo: a.advNo,
    advertiserName: a.advertiser?.nickName ?? null,
    advertiserId: a.advertiser?.userNo ?? null,
    price: a.price,
    asset: a.asset,
    fiat: a.fiatUnit,
    tradeType,
    minAmount: a.minSingleTransAmount ?? null,
    maxAmount: a.maxSingleTransAmount ?? null,
    availableAmount: a.surplusAmount ?? null,
    payTypes: (a.tradeMethods ?? []).map((m) => m.payType).filter(Boolean),
    finishRate: a.advertiser?.monthFinishRate ?? null,
    monthOrderCount: a.advertiser?.monthOrderCount ?? null,
    raw: a,
  };
}

function myAdFromDetail(a: AdDetail): MyAd {
  return {
    advNo: a.advNo,
    asset: a.asset,
    fiat: a.fiatUnit,
    tradeType: a.tradeType,
    price: a.price,
    priceType: a.priceType ?? null,
    status: a.advStatus ?? null,
    surplusAmount: a.surplusAmount ?? null,
    minAmount: a.minSingleTransAmount ?? null,
    maxAmount: a.maxSingleTransAmount ?? null,
    raw: a,
  };
}

function orderFromDetail(o: OrderDetail): OrderRecord {
  const status = o.orderStatus ?? null;
  return {
    orderNo: o.orderNumber,
    advNo: o.advNo ?? null,
    tradeType: o.tradeType,
    asset: o.asset,
    fiat: o.fiat,
    amount: o.amount ?? null,
    totalPrice: o.totalPrice ?? null,
    status,
    statusText: typeof o.orderStatusText === "string" ? o.orderStatusText : null,
    counterpartyRef: pickString(o, ["buyerNickname", "sellerNickname", "counterpartNo"]),
    counterpartyName: pickString(o, ["buyerName", "sellerName", "buyerNickname", "sellerNickname"]),
    // Status numérico de "pago" varia; confirmar contra a doc. Heurística por texto.
    isPaid: /pa(id|y)/i.test(String(o.orderStatusText ?? "")),
    createTime: typeof o.createTime === "number" ? o.createTime : null,
    raw: o,
  };
}

function chatFromMessage(m: ChatMessage): ChatMsg {
  return {
    id: String(m.id),
    orderNo: m.orderNo,
    direction: m.self ? "out" : "in",
    type: m.type ?? "text",
    content: m.content ?? "",
    createTime: m.createTime ?? Date.now(),
    raw: m,
  };
}

function pickString(obj: Record<string, unknown>, keys: string[]): string | null {
  for (const k of keys) {
    const v = obj[k];
    if (typeof v === "string" && v) return v;
  }
  return null;
}

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