import { env } from "../config/env.js";
import { exchangeClientForAccount } from "../exchange/index.js";
import { getSpotPrice } from "../binance/spot.js";
import { getSpot, upsertSpot } from "../repos/spot.js";
import { setAdPrice } from "../repos/ads.js";
import { listEnabled, recordAdjustment, touchRun, type PricingConfigRow } from "../repos/pricing.js";
import { writeAudit } from "../audit/audit.js";
import { computeTargetPrice, type PricingCompetitor } from "./engine.js";
import type { MarketAd } from "../exchange/types.js";

/**
 * Worker de pricing: para cada anúncio com pricing ligado, consulta o
 * mercado, aplica as três travas (motor puro) e registra o ajuste. Em
 * DRY-RUN (padrão) não toca na exchange — só grava em price_adjustments.
 */

interface CompetitorFilter {
  payTypes?: string[];
  minMonthOrderCount?: number;
  minFinishRate?: number;
  excludeAdvertiserIds?: string[];
}

export interface PricingRunItem {
  adId: number;
  advNo: string;
  finalPrice: string | null;
  reason: string;
  applied: boolean;
  shouldUpdate: boolean;
  warnings: string[];
  error?: string;
}

/** Roda um ciclo completo. `apply` sobrepõe o padrão do env (dry-run). */
export async function runPricingOnce(apply: boolean = env.PRICING_APPLY): Promise<PricingRunItem[]> {
  const items = await listEnabled();
  const out: PricingRunItem[] = [];

  for (const { ad, config } of items) {
    const item: PricingRunItem = {
      adId: ad.id,
      advNo: ad.adv_no,
      finalPrice: null,
      reason: "",
      applied: false,
      shouldUpdate: false,
      warnings: [],
    };
    try {
      const client = await exchangeClientForAccount(ad.account_id);
      const filter = (config.competitor_filter ?? null) as CompetitorFilter | null;

      const market = await client.searchMarketAds({
        asset: ad.asset,
        fiat: ad.fiat,
        tradeType: ad.trade_type,
        payTypes: filter?.payTypes,
        rows: 20,
      });
      const competitors = market
        .filter((c) => c.advNo !== ad.adv_no)
        .filter((c) => passesFilter(c, filter))
        .map<PricingCompetitor>((c) => ({
          advNo: c.advNo,
          advertiserId: c.advertiserId,
          price: c.price,
        }));

      const spotPrice = await resolveSpot(config);

      const decision = computeTargetPrice({
        tradeType: ad.trade_type,
        currentPrice: ad.price,
        competitors,
        spotPrice,
        config: {
          followStrategy: config.follow_strategy,
          beatAmount: config.beat_amount,
          targetRank: config.target_rank,
          minProfitPrice: config.min_profit_price,
          spotGuardEnabled: !!config.spot_guard_enabled,
          spotMarginRatio: config.spot_margin_ratio,
          minPriceStep: config.min_price_step,
        },
      });

      const willApply = apply && decision.shouldUpdate && decision.finalPrice !== null;

      await recordAdjustment({
        adId: ad.id,
        oldPrice: ad.price,
        newPrice: decision.finalPrice,
        competitorPrice: decision.competitorPrice,
        spotPrice,
        reason: decision.warnings.length ? `${decision.reason} | ${decision.warnings.join("; ")}` : decision.reason,
        applied: willApply,
      });

      if (willApply && decision.finalPrice) {
        await client.updateAdPrice(ad.adv_no, decision.finalPrice);
        await setAdPrice(ad.id, decision.finalPrice);
        await writeAudit({
          action: "pricing.apply",
          entity: "ad",
          entityId: ad.id,
          detail: { advNo: ad.adv_no, from: ad.price, to: decision.finalPrice, reason: decision.reason },
        });
      }

      await touchRun(ad.id);

      item.finalPrice = decision.finalPrice;
      item.reason = decision.reason;
      item.applied = willApply;
      item.shouldUpdate = decision.shouldUpdate;
      item.warnings = decision.warnings;
    } catch (err) {
      item.error = err instanceof Error ? err.message : String(err);
    }
    out.push(item);
  }

  return out;
}

/** Resolve o preço spot: cache (<60s) ou busca pública, com fallback. */
async function resolveSpot(config: PricingConfigRow): Promise<string | null> {
  if (!config.spot_guard_enabled || !config.spot_symbol) return null;
  const symbol = config.spot_symbol;
  try {
    const cached = await getSpot(symbol);
    if (cached && Date.now() - new Date(cached.fetched_at).getTime() < 60_000) {
      return cached.price;
    }
    const fresh = await getSpotPrice(symbol);
    await upsertSpot(symbol, fresh);
    return fresh;
  } catch {
    const cached = await getSpot(symbol);
    return cached?.price ?? null; // fallback ao último conhecido (ou null)
  }
}

function passesFilter(ad: MarketAd, filter: CompetitorFilter | null): boolean {
  if (!filter) return true;
  if (filter.excludeAdvertiserIds && ad.advertiserId && filter.excludeAdvertiserIds.includes(ad.advertiserId)) {
    return false;
  }
  if (filter.minMonthOrderCount != null && (ad.monthOrderCount ?? 0) < filter.minMonthOrderCount) {
    return false;
  }
  if (filter.minFinishRate != null && (ad.finishRate ?? 0) < filter.minFinishRate) {
    return false;
  }
  return true;
}
