/**
 * Unified Amap (高德地图) API client.
 * All external calls go through amapFetch() with consistent timeout + error handling.
 */
import { ApiError } from "@/lib/api";

const AMAP_TIMEOUT_MS = 8000;

export function requireAmapApiKey(): string {
  const key = process.env.AMAP_API_KEY;
  if (!key) throw new ApiError("服务配置异常，请稍后重试", 500);
  return key;
}

async function amapFetch(url: URL): Promise<Record<string, unknown>> {
  let res: Response;
  try {
    res = await fetch(url.toString(), {
      signal: AbortSignal.timeout(AMAP_TIMEOUT_MS),
    });
  } catch {
    throw new ApiError("位置服务暂时不可用，请稍后重试", 503);
  }
  const data = await res.json();
  return data as Record<string, unknown>;
}

// ---------------------------------------------------------------------------
// Shared POI mapper
// ---------------------------------------------------------------------------

interface RawPoi {
  id?: string;
  name: string;
  address?: string;
  location?: string;
  type?: string;
  business?: { rating?: string; cost?: string; tel?: string };
}

export interface PoiResult {
  id?: string;
  name: string;
  address: string;
  lat: number;
  lng: number;
  rating?: number | null;
  cost?: number | null;
}

function mapPois(pois: RawPoi[], opts?: { includeCost?: boolean }): PoiResult[] {
  return pois
    .filter((p) => p.location)
    .map((p) => {
      const [lng, lat] = (p.location ?? "0,0").split(",").map(Number);
      const ratingStr = p.business?.rating;
      const rating =
        ratingStr && ratingStr !== "[]" ? parseFloat(ratingStr) || null : null;
      const result: PoiResult = {
        id: p.id,
        name: p.name,
        address: p.address || "",
        lat,
        lng,
        rating,
      };
      if (opts?.includeCost) {
        const costStr = p.business?.cost;
        result.cost =
          costStr && costStr !== "[]" && costStr !== "0"
            ? Number(costStr)
            : null;
      }
      return result;
    });
}

// ---------------------------------------------------------------------------
// POI search — text (v5)
// ---------------------------------------------------------------------------

export async function searchPlaceText(params: {
  keywords: string;
  city?: string;
  types?: string;
  location?: string;
  pageSize?: number;
}): Promise<PoiResult[]> {
  const url = new URL("https://restapi.amap.com/v5/place/text");
  url.searchParams.set("key", requireAmapApiKey());
  url.searchParams.set("keywords", params.keywords);
  url.searchParams.set("show_fields", "business");
  url.searchParams.set("page_size", String(params.pageSize ?? 10));
  if (params.city) url.searchParams.set("region", params.city);
  if (params.types) url.searchParams.set("types", params.types);
  if (params.location) url.searchParams.set("location", params.location);

  const data = await amapFetch(url);
  if (data.status !== "1" || !(data.pois as RawPoi[] | undefined)?.length) {
    return [];
  }
  return mapPois(data.pois as RawPoi[], { includeCost: true });
}

// ---------------------------------------------------------------------------
// POI search — around (v5)
// ---------------------------------------------------------------------------

export async function searchPlaceAround(params: {
  keywords: string;
  location: string;
  radius?: number;
  pageSize?: number;
}): Promise<PoiResult[]> {
  const url = new URL("https://restapi.amap.com/v5/place/around");
  url.searchParams.set("key", requireAmapApiKey());
  url.searchParams.set("location", params.location);
  url.searchParams.set("keywords", params.keywords);
  url.searchParams.set("radius", String(params.radius ?? 5000));
  url.searchParams.set("show_fields", "business");
  url.searchParams.set("page_size", String(params.pageSize ?? 8));

  const data = await amapFetch(url);
  if (data.status !== "1" || !(data.pois as RawPoi[] | undefined)?.length) {
    return [];
  }
  return mapPois(data.pois as RawPoi[]);
}

// ---------------------------------------------------------------------------
// Input tips (v3)
// ---------------------------------------------------------------------------

export interface InputTipResult {
  id: string;
  name: string;
  district: string;
  address: string;
  lat: number;
  lng: number;
}

export async function getInputTips(params: {
  keywords: string;
  location?: string;
}): Promise<InputTipResult[]> {
  const url = new URL("https://restapi.amap.com/v3/assistant/inputtips");
  url.searchParams.set("key", requireAmapApiKey());
  url.searchParams.set("keywords", params.keywords);
  url.searchParams.set("datatype", "poi");
  if (params.location) url.searchParams.set("location", params.location);

  const data = await amapFetch(url);
  if (data.status !== "1" || !data.tips) return [];

  return (
    data.tips as {
      id: string;
      name: string;
      district?: string;
      address?: string;
      location?: string;
    }[]
  )
    .filter((t) => t.location && t.location !== "")
    .slice(0, 8)
    .map((t) => {
      const [lng, lat] = t.location!.split(",").map(Number);
      return {
        id: t.id,
        name: t.name,
        district: t.district || "",
        address: t.address || "",
        lat,
        lng,
      };
    });
}

// ---------------------------------------------------------------------------
// Reverse geocode (v3)
// ---------------------------------------------------------------------------

export interface RegeoResult {
  name: string | null;
  formatted: string | null;
}

export async function reverseGeocode(params: {
  lat: number;
  lng: number;
}): Promise<RegeoResult> {
  const url = new URL("https://restapi.amap.com/v3/geocode/regeo");
  url.searchParams.set("key", requireAmapApiKey());
  url.searchParams.set("location", `${params.lng},${params.lat}`);
  url.searchParams.set("extensions", "base");

  const data = await amapFetch(url);
  if (data.status !== "1" || !data.regeocode) {
    return { name: null, formatted: null };
  }

  const regeocode = data.regeocode as Record<string, unknown>;
  const comp = regeocode.addressComponent as Record<string, unknown> | undefined;
  const district = (comp?.district || comp?.city || "") as string;
  const township = (comp?.township || "") as string;
  const neighborhood = (
    (comp?.neighborhood as Record<string, unknown> | undefined)?.name || ""
  ) as string;

  const name = [district, township, neighborhood]
    .filter(Boolean)
    .join(" ")
    .trim();

  return {
    name: name || (regeocode.formatted_address as string) || null,
    formatted: (regeocode.formatted_address as string) || null,
  };
}

// ---------------------------------------------------------------------------
// Transit direction (v3)
// ---------------------------------------------------------------------------

export interface TransitResult {
  durationMin: number;
  distanceKm: number;
  description: string;
  mode: string;
}

export function parseTransitSegments(
  segments: Record<string, unknown>[],
): { description: string; mode: string } {
  const parts: string[] = [];
  let hasSubway = false;
  let hasBus = false;

  for (const seg of segments) {
    const bus = seg.bus as
      | { buslines?: Record<string, unknown>[] }
      | undefined;
    if (!bus?.buslines?.length) continue;
    for (const line of bus.buslines) {
      const rawName = String(line.name ?? "");
      const name = rawName.replace(/\(.*?\)$/, "").trim();
      const viaNum = Number(line.via_num ?? 0);
      const isSubway =
        String(line.type ?? "").includes("地铁") ||
        String(line.type ?? "").includes("轨道");
      if (isSubway) hasSubway = true;
      else hasBus = true;
      const dep = (line.departure_stop as Record<string, unknown> | undefined)
        ?.name;
      const arr = (line.arrival_stop as Record<string, unknown> | undefined)
        ?.name;
      const stops = dep && arr ? ` ${dep}→${arr}` : "";
      parts.push(
        viaNum > 0 ? `${name}${stops}(${viaNum}站)` : `${name}${stops}`,
      );
    }
  }

  const mode = hasSubway ? "地铁" : hasBus ? "公交" : "步行";
  const description = parts.length > 0 ? parts.join(" → ") : mode;
  return { description, mode };
}

export async function getTransitDirection(params: {
  originLat: number;
  originLng: number;
  destLat: number;
  destLng: number;
  city: string;
  cityDest?: string;
}): Promise<TransitResult | null> {
  const url = new URL(
    "https://restapi.amap.com/v3/direction/transit/integrated",
  );
  url.searchParams.set("key", requireAmapApiKey());
  url.searchParams.set("origin", `${params.originLng},${params.originLat}`);
  url.searchParams.set(
    "destination",
    `${params.destLng},${params.destLat}`,
  );
  url.searchParams.set("city", params.city);
  url.searchParams.set("cityd", params.cityDest ?? params.city);
  url.searchParams.set("output", "json");

  const data = await amapFetch(url);
  if (data.status !== "1") return null;

  const route = data.route as Record<string, unknown> | undefined;
  const transits = route?.transits as Record<string, unknown>[] | undefined;
  if (!transits?.length) return null;

  const transit = transits[0];
  const durationMin = Math.ceil(Number(transit.duration) / 60);
  const distanceKm =
    Math.round(Number(route!.distance) / 100) / 10;
  const { description, mode } = parseTransitSegments(
    (transit.segments as Record<string, unknown>[]) ?? [],
  );
  return { durationMin, distanceKm, description, mode };
}

// ---------------------------------------------------------------------------
// Convenience: POI search used by plan generation (dispatches text vs around)
// ---------------------------------------------------------------------------

export async function searchPois(
  query: string,
  searchType: string,
  anchorLat: number,
  anchorLng: number,
): Promise<PoiResult[]> {
  const location = `${anchorLng},${anchorLat}`;
  if (searchType === "category") {
    return searchPlaceAround({
      keywords: query,
      location,
      pageSize: 8,
    });
  }
  return searchPlaceText({
    keywords: query,
    location,
    pageSize: 8,
  });
}
