import type { NormalizedTelemetry } from "@tracker/shared";

import { encodeTrackerKingAck } from "./encoder.js";
import type { ProtocolParser } from "../types.js";

const cleanText = (payload: Buffer) => payload.toString("utf8").replace(/\0/g, "").trim();

const parseCoordinate = (value: string, hemisphere?: string, isLatitude = true): number | null => {
  const normalized = value.trim();
  if (!normalized) {
    return null;
  }

  const direct = Number.parseFloat(normalized);
  if (Number.isFinite(direct) && (!hemisphere || Math.abs(direct) <= (isLatitude ? 90 : 180))) {
    return hemisphere === "S" || hemisphere === "W" ? -Math.abs(direct) : direct;
  }

  const raw = Number.parseFloat(normalized);
  if (!Number.isFinite(raw)) {
    return null;
  }

  const degreesDigits = isLatitude ? 2 : 3;
  const stringValue = normalized.includes(".") ? normalized : `${normalized}.0`;
  const [wholePart, fractionPart] = stringValue.split(".");
  if (wholePart.length <= degreesDigits) {
    return null;
  }

  const degrees = Number.parseFloat(wholePart.slice(0, degreesDigits));
  const minutes = Number.parseFloat(`${wholePart.slice(degreesDigits)}.${fractionPart ?? "0"}`);
  if (!Number.isFinite(degrees) || !Number.isFinite(minutes)) {
    return null;
  }

  const decimal = degrees + minutes / 60;
  return hemisphere === "S" || hemisphere === "W" ? -decimal : decimal;
};

const parseTimestamp = (tokens: string[]): string | undefined => {
  const compact = tokens.find((token) => /^\d{12,14}$/.test(token));
  if (!compact) {
    return undefined;
  }

  const yearPrefix = compact.length === 12 ? "20" : "";
  const timestamp = `${yearPrefix}${compact}`;
  const year = timestamp.slice(0, 4);
  const month = timestamp.slice(4, 6);
  const day = timestamp.slice(6, 8);
  const hour = timestamp.slice(8, 10);
  const minute = timestamp.slice(10, 12);
  const second = timestamp.slice(12, 14);
  return `${year}-${month}-${day}T${hour}:${minute}:${second}Z`;
};

const buildTelemetry = (rawPayload: string, imei: string): NormalizedTelemetry | undefined => {
  const sanitized = rawPayload.replace(/;$/, "");
  const tokens = sanitized.split(",").map((token) => token.trim()).filter(Boolean);
  const hemisphereIndex = tokens.findIndex((token) => token === "N" || token === "S");

  let lat: number | null = null;
  let lon: number | null = null;
  let speed: number | undefined;
  let heading: number | undefined;

  if (hemisphereIndex > 0 && hemisphereIndex + 2 < tokens.length) {
    lat = parseCoordinate(tokens[hemisphereIndex - 1] ?? "", tokens[hemisphereIndex], true);
    lon = parseCoordinate(tokens[hemisphereIndex + 1] ?? "", tokens[hemisphereIndex + 2], false);
    speed = Number.parseFloat(tokens[hemisphereIndex + 3] ?? "");
    heading = Number.parseFloat(tokens[hemisphereIndex + 4] ?? "");
  }

  if (lat == null || lon == null) {
    const latMatch = sanitized.match(/lat[:=](-?\d+(?:\.\d+)?)/i);
    const lonMatch = sanitized.match(/lon[:=](-?\d+(?:\.\d+)?)/i);
    lat = latMatch ? Number.parseFloat(latMatch[1]) : null;
    lon = lonMatch ? Number.parseFloat(lonMatch[1]) : null;
  }

  if (lat == null || lon == null || !Number.isFinite(lat) || !Number.isFinite(lon)) {
    return undefined;
  }

  const deviceTime = parseTimestamp(tokens);
  const batteryMatch = sanitized.match(/(?:bat|battery)[:=](\d+(?:\.\d+)?)/i);
  const gsmMatch = sanitized.match(/(?:gsm|signal|csq)[:=](\d+(?:\.\d+)?)/i);
  const ignition = /(?:acc|ignition)[:=](1|on|true)/i.test(sanitized)
    ? true
    : /(?:acc|ignition)[:=](0|off|false)/i.test(sanitized)
      ? false
      : undefined;

  return {
    imei,
    protocolName: "trackerking",
    rawPayload,
    deviceTime,
    serverTime: new Date().toISOString(),
    lat,
    lon,
    speed: Number.isFinite(speed) ? speed : undefined,
    heading: Number.isFinite(heading) ? heading : undefined,
    ignition,
    battery: batteryMatch ? Number.parseFloat(batteryMatch[1]) : undefined,
    gsmSignal: gsmMatch ? Number.parseFloat(gsmMatch[1]) : undefined,
  };
};

export const trackerKingProtocol: ProtocolParser = {
  name: "trackerking",
  matches: (payload) => {
    const text = cleanText(payload);
    return text.includes("imei") || text.startsWith("##") || /,\s*[NS]\s*,/i.test(text);
  },
  parse: (payload) => {
    const rawPayload = cleanText(payload);
    if (!rawPayload) {
      return null;
    }

    const imeiMatch = rawPayload.match(/imei:?\s*(\d{14,17})/i) ?? rawPayload.match(/\b(\d{14,17})\b/);
    const imei = imeiMatch?.[1];
    const isLogin = rawPayload.startsWith("##");
    const isHeartbeat = /heartbeat|keepalive|alive/i.test(rawPayload);

    const telemetry = imei ? buildTelemetry(rawPayload, imei) : undefined;
    const eventType = isLogin ? "login" : telemetry ? "telemetry" : isHeartbeat ? "heartbeat" : "unknown";
    const ackPayload = encodeTrackerKingAck(eventType);

    return {
      protocolName: "trackerking",
      rawPayload,
      imei,
      telemetry,
      requiresAck: ackPayload.length > 0,
      ackPayload: ackPayload.length > 0 ? ackPayload : undefined,
      eventType,
    };
  },
};
