import type { AisTarget } from "../../../entities/aisTarget/model/types";
import type { LegacyVesselIndex } from "../../../entities/legacyVessel/lib";
import { matchLegacyVessel } from "../../../entities/legacyVessel/lib";
import type { LegacyVesselInfo } from "../../../entities/legacyVessel/model/types";
import type { ZonesGeoJson } from "../../../entities/zone/api/useZones";
import type { ZoneId } from "../../../entities/zone/model/meta";
import { ZONE_IDS } from "../../../entities/zone/model/meta";
import { VESSEL_TYPES } from "../../../entities/vessel/model/meta";
import type { VesselTypeCode } from "../../../entities/vessel/model/types";
import { haversineNm } from "../../../shared/lib/geo/haversineNm";
import { pointInMultiPolygon } from "../../../shared/lib/geo/pointInPolygon";
import type { DerivedLegacyVessel, DerivedVesselState, FcLink, FleetCircle, LegacyAlarm, PairLink } from "./types";

function isFiniteNumber(x: unknown): x is number {
  return typeof x === "number" && Number.isFinite(x);
}

export function deriveVesselState(shipCode: VesselTypeCode, sogRaw: unknown): DerivedVesselState {
  const sog = isFiniteNumber(sogRaw) ? sogRaw : null;
  if (sog === null) return { label: "미상", isFishing: false, isTransit: false };
  if (sog < 0.5) return { label: "정지", isFishing: false, isTransit: false };

  const meta = VESSEL_TYPES[shipCode];
  const inPrimary = meta.speedProfile.some((s) => s.primary && sog >= s.range[0] && sog <= s.range[1]);
  if (inPrimary) return { label: "조업", isFishing: true, isTransit: false };

  if (sog >= 5) return { label: "항해", isFishing: false, isTransit: true };
  return { label: "저속", isFishing: false, isTransit: false };
}

export function buildLegacyHitMap(targets: AisTarget[], legacyIndex: LegacyVesselIndex | null): Map<number, LegacyVesselInfo> {
  const hits = new Map<number, LegacyVesselInfo>();
  if (!legacyIndex) return hits;
  for (const t of targets) {
    if (typeof t.mmsi !== "number") continue;
    const hit = matchLegacyVessel(t, legacyIndex);
    if (!hit) continue;
    hits.set(t.mmsi, hit);
  }
  return hits;
}

export function findZoneId(lon: number, lat: number, zones: ZonesGeoJson | null): ZoneId | null {
  if (!zones) return null;
  for (const f of zones.features) {
    const zoneId = (f.properties as { zoneId?: string } | undefined)?.zoneId as ZoneId | undefined;
    if (!zoneId) continue;
    if (!ZONE_IDS.includes(zoneId)) continue;
    const geom = f.geometry;
    if (!geom || geom.type !== "MultiPolygon") continue;
    if (pointInMultiPolygon(lon, lat, geom.coordinates as unknown as [number, number][][][])) return zoneId;
  }
  return null;
}

export function deriveLegacyVessels(args: {
  targets: AisTarget[];
  legacyHits: Map<number, LegacyVesselInfo>;
  zones: ZonesGeoJson | null;
}): DerivedLegacyVessel[] {
  const out: DerivedLegacyVessel[] = [];
  for (const t of args.targets) {
    if (typeof t.mmsi !== "number") continue;
    const legacy = args.legacyHits.get(t.mmsi);
    if (!legacy) continue;

    const lat = isFiniteNumber(t.lat) ? t.lat : null;
    const lon = isFiniteNumber(t.lon) ? t.lon : null;
    if (lat === null || lon === null) continue;

    const code = legacy.shipCode as VesselTypeCode;
    if (!code || !(code in VESSEL_TYPES)) continue;

    const ownerKey = (legacy.ownerRoman || legacy.ownerCn || "").trim() || null;

    out.push({
      mmsi: t.mmsi,
      name: (t.name || "").trim() || legacy.shipNameCn || legacy.shipNameRoman || "(no name)",
      callsign: (t.callsign || "").trim() || null,
      lat,
      lon,
      sog: isFiniteNumber(t.sog) ? t.sog : null,
      cog: isFiniteNumber(t.cog) ? t.cog : null,
      heading: isFiniteNumber(t.heading) ? t.heading : null,
      messageTimestamp: t.messageTimestamp ?? null,
      receivedDate: t.receivedDate ?? null,
      ais: t,
      legacy,
      shipCode: code,
      permitNo: legacy.permitNo,
      ownerKey,
      ownerCn: legacy.ownerCn ?? null,
      ownerRoman: legacy.ownerRoman ?? null,
      workSeaArea: legacy.workSeaArea ?? null,
      pairPermitNo: legacy.pairPermitNo ?? null,
      zoneId: findZoneId(lon, lat, args.zones),
      state: deriveVesselState(code, t.sog),
    });
  }
  return out;
}

export function filterByShipCode(vessels: DerivedLegacyVessel[], selected: VesselTypeCode | null): DerivedLegacyVessel[] {
  if (!selected) return vessels;
  if (selected === "PT" || selected === "PT-S") return vessels.filter((v) => v.shipCode === "PT" || v.shipCode === "PT-S");
  return vessels.filter((v) => v.shipCode === selected);
}

export function filterByShipCodes(vessels: DerivedLegacyVessel[], enabled: Record<VesselTypeCode, boolean>): DerivedLegacyVessel[] {
  return vessels.filter((v) => enabled[v.shipCode]);
}

export function computeCountsByType(vessels: DerivedLegacyVessel[]) {
  const counts: Record<VesselTypeCode, number> = { PT: 0, "PT-S": 0, GN: 0, OT: 0, PS: 0, FC: 0 };
  for (const v of vessels) counts[v.shipCode] += 1;
  return counts;
}

export function computePairLinks(vessels: DerivedLegacyVessel[]): PairLink[] {
  const byPermit = new Map<string, DerivedLegacyVessel>();
  for (const v of vessels) byPermit.set(v.permitNo, v);

  const seen = new Set<string>();
  const links: PairLink[] = [];

  for (const v of vessels) {
    if (!v.pairPermitNo) continue;
    const pair = byPermit.get(v.pairPermitNo);
    if (!pair) continue;
    const a = v.mmsi < pair.mmsi ? v : pair;
    const b = v.mmsi < pair.mmsi ? pair : v;
    const key = `${a.mmsi}-${b.mmsi}`;
    if (seen.has(key)) continue;
    seen.add(key);

    const d = haversineNm(a.lat, a.lon, b.lat, b.lon);
    links.push({
      aMmsi: a.mmsi,
      bMmsi: b.mmsi,
      from: [a.lon, a.lat],
      to: [b.lon, b.lat],
      distanceNm: d,
      warn: d > 3,
    });
  }

  return links;
}

export function computeFcLinks(vessels: DerivedLegacyVessel[]): FcLink[] {
  const others = vessels.filter((v) => v.shipCode !== "FC");
  const fcs = vessels.filter((v) => v.shipCode === "FC");

  const links: FcLink[] = [];
  for (const fc of fcs) {
    let best: DerivedLegacyVessel | null = null;
    let bestD = Infinity;
    for (const o of others) {
      const d = haversineNm(fc.lat, fc.lon, o.lat, o.lon);
      if (d < bestD) {
        bestD = d;
        best = o;
      }
    }
    if (!best || !Number.isFinite(bestD)) continue;
    if (bestD > 5) continue;
    links.push({
      fcMmsi: fc.mmsi,
      otherMmsi: best.mmsi,
      from: [fc.lon, fc.lat],
      to: [best.lon, best.lat],
      distanceNm: bestD,
      suspicious: bestD < 0.5,
    });
  }
  return links;
}

export function computeFleetCircles(vessels: DerivedLegacyVessel[]): FleetCircle[] {
  const groups = new Map<string, DerivedLegacyVessel[]>();
  for (const v of vessels) {
    if (!v.ownerKey) continue;
    const list = groups.get(v.ownerKey);
    if (list) list.push(v);
    else groups.set(v.ownerKey, [v]);
  }

  const out: FleetCircle[] = [];
  for (const [ownerKey, vs] of groups.entries()) {
    if (vs.length < 3) continue;
    const ownerLabel =
      vs.find((v) => v.ownerCn)?.ownerCn ??
      vs.find((v) => v.ownerRoman)?.ownerRoman ??
      ownerKey;
    const lon = vs.reduce((sum, v) => sum + v.lon, 0) / vs.length;
    const lat = vs.reduce((sum, v) => sum + v.lat, 0) / vs.length;
    let radiusNm = 0;
    for (const v of vs) radiusNm = Math.max(radiusNm, haversineNm(lat, lon, v.lat, v.lon));
    out.push({
      ownerKey,
      ownerLabel,
      center: [lon, lat],
      radiusNm: Math.max(0.2, radiusNm),
      count: vs.length,
      vesselMmsis: vs.map((v) => v.mmsi),
    });
  }

  // Show largest fleets first.
  out.sort((a, b) => b.count - a.count);
  return out.slice(0, 30);
}

function fmtAgoLabel(nowMs: number, iso: string | null): string {
  if (!iso) return "-";
  const t = Date.parse(iso);
  if (!Number.isFinite(t)) return "-";
  const diffMin = Math.max(0, Math.round((nowMs - t) / 60_000));
  if (diffMin <= 0) return "방금";
  return `-${diffMin}분`;
}

export function computeLegacyAlarms(args: {
  vessels: DerivedLegacyVessel[];
  pairLinks: PairLink[];
  fcLinks: FcLink[];
  now?: Date;
}): LegacyAlarm[] {
  const nowMs = (args.now ?? new Date()).getTime();
  const month = new Date(nowMs).getMonth(); // 0-11

  const alarms: LegacyAlarm[] = [];

  for (const p of args.pairLinks) {
    if (!p.warn) continue;
    const a = args.vessels.find((v) => v.mmsi === p.aMmsi);
    const b = args.vessels.find((v) => v.mmsi === p.bMmsi);
    const ts = a?.messageTimestamp ?? b?.messageTimestamp ?? null;
    alarms.push({
      severity: "hi",
      kind: "pair_separation",
      timeLabel: fmtAgoLabel(nowMs, ts),
      text: `${a?.permitNo ?? p.aMmsi}↔${b?.permitNo ?? p.bMmsi} 쌍분리 ${p.distanceNm.toFixed(1)}NM`,
      relatedMmsi: [p.aMmsi, p.bMmsi],
    });
  }

  for (const l of args.fcLinks) {
    if (!l.suspicious) continue;
    const fc = args.vessels.find((v) => v.mmsi === l.fcMmsi);
    const o = args.vessels.find((v) => v.mmsi === l.otherMmsi);
    const ts = fc?.messageTimestamp ?? o?.messageTimestamp ?? null;
    alarms.push({
      severity: "hi",
      kind: "transshipment",
      timeLabel: fmtAgoLabel(nowMs, ts),
      text: `${fc?.permitNo ?? l.fcMmsi}→${o?.permitNo ?? l.otherMmsi} 환적의심 ${l.distanceNm.toFixed(2)}NM`,
      relatedMmsi: [l.fcMmsi, l.otherMmsi],
    });
  }

  for (const v of args.vessels) {
    const meta = VESSEL_TYPES[v.shipCode];
    if (meta.monthlyIntensity[month] !== 0) continue;
    if (!v.state.isFishing) continue;
    alarms.push({
      severity: "cr",
      kind: "closed_season",
      timeLabel: fmtAgoLabel(nowMs, v.messageTimestamp),
      text: `${v.permitNo} [${v.shipCode}] 휴어기 조업 의심 ${v.sog ?? "?"}kt`,
      relatedMmsi: [v.mmsi],
    });
  }

  // AIS stale
  for (const v of args.vessels) {
    const ts = Date.parse(v.messageTimestamp || "");
    if (!Number.isFinite(ts)) continue;
    const diffMin = (nowMs - ts) / 60_000;
    if (diffMin < 45) continue;
    alarms.push({
      severity: "cr",
      kind: "ais_stale",
      timeLabel: fmtAgoLabel(nowMs, v.messageTimestamp),
      text: `${v.permitNo} [${v.shipCode}] AIS 지연 ${Math.round(diffMin)}분`,
      relatedMmsi: [v.mmsi],
    });
  }

  // Zone violations (only when we can detect zone).
  for (const v of args.vessels) {
    if (!v.zoneId) continue;
    const allowed = VESSEL_TYPES[v.shipCode].allowedZones;
    if (allowed.includes(v.zoneId)) continue;
    alarms.push({
      severity: "hi",
      kind: "zone_violation",
      timeLabel: fmtAgoLabel(nowMs, v.messageTimestamp),
      text: `${v.permitNo} [${v.shipCode}] 수역 이탈 (${v.zoneId})`,
      relatedMmsi: [v.mmsi],
    });
  }

  // Fixed category priority (independent of severity):
  // 1) 수역 이탈 2) 쌍 이격 경고 3) 환적 의심 4) 휴어기 조업 의심 5) AIS 지연
  // Within each category: most recent first (smaller N in "-N분" is more recent).
  const kindPriority: Record<LegacyAlarm["kind"], number> = {
    zone_violation: 0,
    pair_separation: 1,
    transshipment: 2,
    closed_season: 3,
    ais_stale: 4,
  };

  const parseAgeMin = (label: string) => {
    if (label === "방금") return 0;
    const m = /-(\\d+)분/.exec(label);
    if (m) return Number(m[1]);
    return Number.POSITIVE_INFINITY;
  };

  alarms.sort((a, b) => {
    const ak = kindPriority[a.kind] ?? 999;
    const bk = kindPriority[b.kind] ?? 999;
    if (ak !== bk) return ak - bk;
    const am = parseAgeMin(a.timeLabel);
    const bm = parseAgeMin(b.timeLabel);
    if (am !== bm) return am - bm;
    // Stable tie-break.
    return a.text.localeCompare(b.text);
  });

  return alarms;
}
