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

import { encodeGt06Ack } from "./encoder.js";
import type { ProtocolContext, ProtocolParser } from "../types.js";

const toHex = (payload: Buffer) => payload.toString("hex");

const isGt06Packet = (payload: Buffer) =>
  payload.length >= 5
  && ((payload[0] === 0x78 && payload[1] === 0x78) || (payload[0] === 0x79 && payload[1] === 0x79));

const getHeaderLength = (payload: Buffer) => (payload[0] === 0x79 && payload[1] === 0x79 ? 4 : 3);

const getProtocolNumber = (payload: Buffer) => payload[getHeaderLength(payload)] ?? 0;

const getSerial = (payload: Buffer) => payload.subarray(payload.length - 6, payload.length - 4);

const decodeBcd = (payload: Buffer) => payload.toString("hex").replace(/^0+/, "");

const decodeTimestamp = (payload: Buffer) => {
  if (payload.length < 6) {
    return undefined;
  }

  const [year, month, day, hour, minute, second] = Array.from(payload);
  return `20${String(year).padStart(2, "0")}-${String(month).padStart(2, "0")}-${String(day).padStart(2, "0")}T${String(hour).padStart(2, "0")}:${String(minute).padStart(2, "0")}:${String(second).padStart(2, "0")}Z`;
};

const decodeCoordinates = (value: number) => value / 1_800_000;

const applyHemisphere = (value: number, negative: boolean) => (negative ? -Math.abs(value) : value);

const batteryLevelToPercent = (level: number | undefined) => {
  switch (level) {
    case 0:
      return 0;
    case 1:
      return 10;
    case 2:
      return 25;
    case 3:
      return 50;
    case 4:
      return 75;
    case 5:
      return 90;
    case 6:
      return 100;
    default:
      return undefined;
  }
};

const parseTerminalInfo = (value: number): DeviceStateSnapshot => ({
  ignition: (value & 0x02) === 0x02,
  charging: (value & 0x04) === 0x04,
  defense: (value & 0x01) === 0x01,
  gpsValid: (value & 0x40) === 0x40,
});

const buildTelemetry = (payload: Buffer, context: ProtocolContext): NormalizedTelemetry | undefined => {
  const protocolNumber = getProtocolNumber(payload);
  if (![0x12, 0x16, 0x22].includes(protocolNumber)) {
    return undefined;
  }

  const headerLength = getHeaderLength(payload);
  const infoStart = headerLength + 1;
  const imei = context.imei;
  if (!imei || payload.length < infoStart + 18) {
    return undefined;
  }

  const deviceTime = decodeTimestamp(payload.subarray(infoStart, infoStart + 6));
  const gpsInfo = payload[infoStart + 6] ?? 0;
  const latRaw = payload.readUInt32BE(infoStart + 7);
  const lonRaw = payload.readUInt32BE(infoStart + 11);
  const speed = payload[infoStart + 15];
  const courseStatus = payload.readUInt16BE(infoStart + 16);
  const satelliteCount = gpsInfo & 0x0f;
  const gpsValid = (courseStatus & 0x1000) === 0x1000;

  const lat = applyHemisphere(decodeCoordinates(latRaw), (courseStatus & 0x0400) === 0);
  const lon = applyHemisphere(decodeCoordinates(lonRaw), (courseStatus & 0x0800) !== 0);
  const heading = courseStatus & 0x03ff;

  return {
    imei,
    protocolName: "gt06",
    rawPayload: toHex(payload),
    deviceTime,
    serverTime: new Date().toISOString(),
    lat,
    lon,
    speed,
    heading,
    satelliteCount,
    gpsValid,
    positionType: "GPS",
  };
};

const buildHeartbeatState = (payload: Buffer): DeviceStateSnapshot | undefined => {
  const protocolNumber = getProtocolNumber(payload);
  if (protocolNumber !== 0x13) {
    return undefined;
  }

  const headerLength = getHeaderLength(payload);
  const infoStart = headerLength + 1;
  if (payload.length < infoStart + 3) {
    return undefined;
  }

  const terminalInfo = payload[infoStart] ?? 0;
  const voltageLevel = payload[infoStart + 1];
  const gsmSignal = payload[infoStart + 2];

  return {
    ...parseTerminalInfo(terminalInfo),
    battery: batteryLevelToPercent(voltageLevel),
    gsmSignal,
  };
};

export const gt06Protocol: ProtocolParser = {
  name: "gt06",
  matches: (payload) => isGt06Packet(payload),
  parse: (payload, context = {}) => {
    if (!isGt06Packet(payload)) {
      return null;
    }

    const protocolNumber = getProtocolNumber(payload);
    const serial = getSerial(payload);
    const rawPayload = toHex(payload);

    if (protocolNumber === 0x01) {
      const headerLength = getHeaderLength(payload);
      const imei = decodeBcd(payload.subarray(headerLength + 1, headerLength + 9));
      return {
        protocolName: "gt06",
        rawPayload,
        imei,
        requiresAck: true,
        ackPayload: encodeGt06Ack(protocolNumber, serial),
        eventType: "login",
      };
    }

    if (protocolNumber === 0x13) {
      return {
        protocolName: "gt06",
        rawPayload,
        imei: context.imei,
        deviceState: buildHeartbeatState(payload),
        requiresAck: true,
        ackPayload: encodeGt06Ack(protocolNumber, serial),
        eventType: "heartbeat",
      };
    }

    const telemetry = buildTelemetry(payload, context);

    return {
      protocolName: "gt06",
      rawPayload,
      imei: telemetry?.imei ?? context.imei,
      telemetry,
      deviceState: telemetry
        ? {
            battery: telemetry.battery ?? null,
            gsmSignal: telemetry.gsmSignal ?? null,
            ignition: telemetry.ignition ?? null,
            charging: telemetry.charging ?? null,
            defense: telemetry.defense ?? null,
            gpsValid: telemetry.gpsValid ?? null,
            satelliteCount: telemetry.satelliteCount ?? null,
            positionType: telemetry.positionType ?? null,
            deviceTime: telemetry.deviceTime ?? null,
          }
        : undefined,
      requiresAck: false,
      eventType: telemetry ? "telemetry" : "unknown",
    };
  },
};
