import type { Page, Response } from "playwright";

const AXEL_HOST = "services.mothership.com/axel/location";

export type AxelTraceEvent = {
  at: number;
  phase: string;
  method: string;
  url: string;
  status: number;
  kind: "search" | "place" | "reverse-geocode" | "other";
  placeId?: string;
  searchPlaceIds?: string[];
  searchDescriptions?: string[];
};

function classifyAxelUrl(url: string): AxelTraceEvent["kind"] {
  if (url.includes("/axel/location/search")) {
    return "search";
  }
  if (url.includes("/axel/location/place/reverse-geocode")) {
    return "reverse-geocode";
  }
  if (url.includes("/axel/location/place/")) {
    return "place";
  }
  return "other";
}

function extractPlaceIdFromUrl(url: string): string | undefined {
  if (!url.includes("/axel/location/place/") || url.includes("reverse-geocode")) {
    return undefined;
  }
  const raw = url.split("/axel/location/place/")[1]?.split("?")[0];
  return raw ? decodeURIComponent(raw) : undefined;
}

/** 将 axel/location 网络事件规范化为可 diff 的序列（供人工 vs RPA 对照） */
export function normalizeAxelSequence(events: AxelTraceEvent[]): string[] {
  const out: string[] = [];
  for (const event of events) {
    if (event.kind === "search") {
      const ids = (event.searchPlaceIds ?? []).map((id) => id.slice(0, 24)).join(",");
      const desc = (event.searchDescriptions ?? [])
        .map((d) => d.slice(0, 40))
        .join(" | ");
      out.push(
        `${event.phase} POST search ${event.status}${ids ? ` placeIds=[${ids}]` : ""}${desc ? ` desc=[${desc}]` : ""}`,
      );
      continue;
    }
    if (event.kind === "place") {
      out.push(
        `${event.phase} GET place/${event.placeId ?? "?"} ${event.status}`,
      );
      continue;
    }
    if (event.kind === "reverse-geocode") {
      out.push(`${event.phase} GET reverse-geocode ${event.status}`);
    }
  }
  return out;
}

export function diffAxelTraces(
  manual: AxelTraceEvent[],
  rpa: AxelTraceEvent[],
): {
  manualSequence: string[];
  rpaSequence: string[];
  missingInRpa: string[];
  extraInRpa: string[];
  manualPlaceCommits: string[];
  rpaPlaceCommits: string[];
  verdict: string;
} {
  const manualSequence = normalizeAxelSequence(manual);
  const rpaSequence = normalizeAxelSequence(rpa);

  const manualPlaceCommits = manual
    .filter((e) => e.kind === "place" && e.status === 200 && e.placeId)
    .map((e) => e.placeId!);
  const rpaPlaceCommits = rpa
    .filter((e) => e.kind === "place" && e.status === 200 && e.placeId)
    .map((e) => e.placeId!);

  const manualSet = new Set(manualSequence);
  const rpaSet = new Set(rpaSequence);
  const missingInRpa = manualSequence.filter((line) => !rpaSet.has(line));
  const extraInRpa = rpaSequence.filter((line) => !manualSet.has(line));

  let verdict: string;
  if (manualPlaceCommits.length === 0) {
    verdict = "MANUAL_NO_PLACE_COMMIT";
  } else if (rpaPlaceCommits.length >= 2 && manualPlaceCommits.length >= 2) {
    verdict = "RPA_MATCHES_MANUAL_PLACE_COMMITS";
  } else if (rpaPlaceCommits.length === 0 && manualPlaceCommits.length > 0) {
    verdict = "RPA_MISSING_PLACE_COMMIT";
  } else {
    verdict = "PARTIAL_OR_MISMATCH";
  }

  return {
    manualSequence,
    rpaSequence,
    missingInRpa,
    extraInRpa,
    manualPlaceCommits,
    rpaPlaceCommits,
    verdict,
  };
}

/** 监听 axel/location 请求/响应，记录 search placeId 与 place commit */
export class AxelEventTracer {
  private events: AxelTraceEvent[] = [];

  private phase = "init";

  private responseHandler: ((response: Response) => void) | null = null;

  setPhase(phase: string): void {
    this.phase = phase;
  }

  getPhase(): string {
    return this.phase;
  }

  attach(page: Page): void {
    this.detach();
    this.responseHandler = (response: Response) => {
      void this.onResponse(response);
    };
    page.on("response", this.responseHandler);
  }

  detach(): void {
    this.responseHandler = null;
  }

  getEvents(): readonly AxelTraceEvent[] {
    return this.events;
  }

  private async onResponse(response: Response): Promise<void> {
    const url = response.url();
    if (!url.includes(AXEL_HOST)) {
      return;
    }

    const kind = classifyAxelUrl(url);
    const event: AxelTraceEvent = {
      at: Date.now(),
      phase: this.phase,
      method: response.request().method(),
      url,
      status: response.status(),
      kind,
      placeId: extractPlaceIdFromUrl(url),
    };

    if (kind === "search" && response.status() === 200) {
      try {
        const body = (await response.json()) as {
          googlePlacesResults?: Array<{
            placeId?: string;
            description?: string;
          }>;
        };
        event.searchPlaceIds = (body.googlePlacesResults ?? [])
          .map((row) => row.placeId?.trim())
          .filter((id): id is string => Boolean(id));
        event.searchDescriptions = (body.googlePlacesResults ?? [])
          .map((row) => row.description?.trim())
          .filter((d): d is string => Boolean(d));
      } catch {
        // ignore
      }
    }

    this.events.push(event);
  }
}
