import {
  axelPlaceFetchCandidates,
  normalizeAxelPlaceId,
} from "@/workers/rpa/axel-selection-bridge";
import {
  AXEL_ORIGIN,
  AXEL_PLACE_BASE,
  AXEL_QUOTE_RETRIES,
  AXEL_QUOTE_TIMEOUT_MS,
  AXEL_QUOTE_URL,
  AXEL_REFERER,
  AXEL_SEARCH_URL,
  DEFAULT_AXEL_USER_AGENT,
} from "@/lib/axel/constants";
import {
  pickBestSearchCandidate,
  type AxelSearchCandidate,
} from "@/lib/axel/pick-candidate";
import {
  buildAxelShipmentPayload,
  type AxelCargoInput,
  type AxelPlaceLocation,
} from "@/lib/axel/shipment";
import {
  buildCookieHeader,
  loadStorageState,
  type PlaywrightStorageState,
} from "@/lib/axel/session";
import type { QuoteItem } from "@/modules/providers/quote-provider";
import { decodeAxelQuoteBodyLenient } from "@/workers/rpa/quote-capture/payload-decode";
import { normalizeQuoteItems } from "@/workers/rpa/quote-capture/quote-schema-validator";

export type AxelQuoteCliInput = {
  origin: string;
  dest: string;
  pallets: number;
  weightLb: number;
  lengthIn?: number;
  widthIn?: number;
  heightIn?: number;
  storageStatePath?: string;
};

export type AxelQuoteCliResult = {
  origin: { query: string; placeId: string; description: string };
  dest: { query: string; placeId: string; description: string };
  cargo: AxelCargoInput;
  items: QuoteItem[];
  ratesSummary: Record<string, Record<string, number | string | null>>;
};

type SearchResponseBody = {
  googlePlacesResults?: AxelSearchCandidate[];
};

function defaultCargoDims(input: AxelQuoteCliInput): AxelCargoInput {
  return {
    palletCount: input.pallets,
    weightLb: input.weightLb,
    lengthIn: input.lengthIn ?? 48,
    widthIn: input.widthIn ?? 40,
    heightIn: input.heightIn ?? 45,
  };
}

function summarizeAxelRates(
  body: unknown,
): Record<string, Record<string, number | string | null>> {
  const rates = (body as { data?: { rates?: unknown } })?.data?.rates;
  if (!rates || typeof rates !== "object") {
    return {};
  }
  const out: Record<string, Record<string, number | string | null>> = {};
  for (const [sl, tiers] of Object.entries(rates as Record<string, unknown>)) {
    if (tiers == null || typeof tiers !== "object" || Array.isArray(tiers)) {
      continue;
    }
    out[sl] = {};
    for (const [ro, tier] of Object.entries(tiers as Record<string, unknown>)) {
      if (tier == null || typeof tier !== "object" || Array.isArray(tier)) {
        continue;
      }
      const t = tier as Record<string, unknown>;
      out[sl][ro] =
        typeof t.finalPrice === "number"
          ? t.finalPrice
          : typeof t.price === "number"
            ? t.price
            : null;
      if (typeof t.days === "number") {
        out[sl][`${ro}_days`] = t.days;
      }
    }
  }
  return out;
}

export class AxelHttpClient {
  private readonly cookieHeader: string;

  constructor(state: PlaywrightStorageState) {
    this.cookieHeader = buildCookieHeader(state);
  }

  static fromStorageStatePath(path?: string): AxelHttpClient {
    return new AxelHttpClient(loadStorageState(path));
  }

  /** 缺会话时自动 bootstrap 后再构造客户端 */
  static async create(path?: string): Promise<AxelHttpClient> {
    const { loadStorageStateAsync } = await import("@/lib/axel/session");
    return new AxelHttpClient(await loadStorageStateAsync(path));
  }

  private baseHeaders(): Record<string, string> {
    return {
      Accept: "*/*",
      Origin: AXEL_ORIGIN,
      Referer: AXEL_REFERER,
      "User-Agent": DEFAULT_AXEL_USER_AGENT,
      Cookie: this.cookieHeader,
    };
  }

  async searchLocation(query: string): Promise<AxelSearchCandidate[]> {
    const res = await fetch(AXEL_SEARCH_URL, {
      method: "POST",
      headers: {
        ...this.baseHeaders(),
        "Content-Type": "application/json",
      },
      body: JSON.stringify({ queryString: query }),
    });
    const text = await res.text();
    if (!res.ok) {
      throw new Error(
        `axel/location/search HTTP ${res.status}: ${text.slice(0, 200)}`,
      );
    }
    const body = JSON.parse(text) as SearchResponseBody;
    return body.googlePlacesResults ?? [];
  }

  async fetchPlace(placeId: string): Promise<AxelPlaceLocation> {
    for (const candidate of axelPlaceFetchCandidates(placeId)) {
      const url = `${AXEL_PLACE_BASE}${encodeURIComponent(candidate)}`;
      const res = await fetch(url, { headers: this.baseHeaders() });
      if (!res.ok) {
        continue;
      }
      return (await res.json()) as AxelPlaceLocation;
    }
    throw new Error(
      `axel/location/place 失败 placeId=${normalizeAxelPlaceId(placeId)}`,
    );
  }

  async postQuote(shipment: Record<string, unknown>): Promise<unknown> {
    let lastError: Error | null = null;
    for (let attempt = 0; attempt <= AXEL_QUOTE_RETRIES; attempt += 1) {
      const controller = new AbortController();
      const timer = setTimeout(() => controller.abort(), AXEL_QUOTE_TIMEOUT_MS);
      try {
        const res = await fetch(AXEL_QUOTE_URL, {
          method: "POST",
          headers: {
            ...this.baseHeaders(),
            "Content-Type": "application/json",
          },
          body: JSON.stringify({ shipment }),
          signal: controller.signal,
        });
        const text = await res.text();
        if (!res.ok) {
          throw new Error(
            `axel/quote HTTP ${res.status}: ${text.slice(0, 300)}`,
          );
        }
        return JSON.parse(text) as unknown;
      } catch (err) {
        lastError =
          err instanceof Error ? err : new Error("axel/quote 请求失败");
        if (attempt < AXEL_QUOTE_RETRIES) {
          await new Promise((r) => setTimeout(r, 1_500 * (attempt + 1)));
        }
      } finally {
        clearTimeout(timer);
      }
    }
    throw lastError ?? new Error("axel/quote 请求失败");
  }
}

export async function fetchAxelQuote(
  input: AxelQuoteCliInput,
): Promise<AxelQuoteCliResult> {
  const client = await AxelHttpClient.create(input.storageStatePath);
  const cargo = defaultCargoDims(input);

  const originCandidates = await client.searchLocation(input.origin);
  const destCandidates = await client.searchLocation(input.dest);
  const originPick = pickBestSearchCandidate(input.origin, originCandidates);
  const destPick = pickBestSearchCandidate(input.dest, destCandidates);
  if (!originPick?.placeId) {
    throw new Error(`未找到提货地址候选: ${input.origin}`);
  }
  if (!destPick?.placeId) {
    throw new Error(`未找到派送地址候选: ${input.dest}`);
  }

  const pickup = await client.fetchPlace(originPick.placeId);
  const delivery = await client.fetchPlace(destPick.placeId);
  const shipment = buildAxelShipmentPayload({
    pickup,
    delivery,
    pickupDescription: originPick.description,
    deliveryDescription: destPick.description,
    cargo,
  });

  const body = await client.postQuote(shipment);
  const decoded = decodeAxelQuoteBodyLenient(body);
  if (!decoded?.length) {
    throw new Error("axel/quote 响应无法解析为报价档位");
  }

  return {
    origin: {
      query: input.origin,
      placeId: normalizeAxelPlaceId(originPick.placeId),
      description: originPick.description,
    },
    dest: {
      query: input.dest,
      placeId: normalizeAxelPlaceId(destPick.placeId),
      description: destPick.description,
    },
    cargo,
    items: normalizeQuoteItems(decoded),
    ratesSummary: summarizeAxelRates(body),
  };
}
