import mqtt, { type IClientOptions, type MqttClient } from "mqtt";
import { env } from "../../config/env";
import { createMqttMessage } from "../store/mqttMessageStore";
import { getDeviceBySerialNumber, patchDevice } from "../store/deviceStore";
import { createDeviceHeartbeat } from "../store/heartbeatStore";

type SubscriberStatus = {
  enabled: boolean;
  connected: boolean;
  topics: string[];
  client_id: string | null;
  last_connected_at: string | null;
  last_disconnected_at: string | null;
  last_message_at: string | null;
  last_error: { message: string; at: string } | null;
  received_count: number;
  recorded_count: number;
  failed_count: number;
};

const status: SubscriberStatus = {
  enabled: String(env.MQTT_SUBSCRIBE_ENABLED).toLowerCase() === "true",
  connected: false,
  topics: env.MQTT_SUBSCRIBE_TOPICS.split(",").map((topic) => topic.trim()).filter(Boolean),
  client_id: null,
  last_connected_at: null,
  last_disconnected_at: null,
  last_message_at: null,
  last_error: null,
  received_count: 0,
  recorded_count: 0,
  failed_count: 0
};

let clientRef: MqttClient | null = null;
let started = false;

type ParsedMqttTopic = {
  device_id: string;
  message_type: string;
  serial_number?: string;
  is_heartbeat?: boolean;
};

async function parseTopic(topic: string): Promise<ParsedMqttTopic | null> {
  const deviceTopicMatch = topic.match(/^devices\/([^/]+)\/uplink\/(.+)$/);
  if (deviceTopicMatch) {
    return {
      device_id: deviceTopicMatch[1],
      message_type: deviceTopicMatch[2].replace(/\//g, "_")
    };
  }

  const soundboxHeartbeatMatch = topic.match(/^soundbox\/([^/]+)\/down\/heartbeat$/);
  if (soundboxHeartbeatMatch) {
    const device = await getDeviceBySerialNumber(soundboxHeartbeatMatch[1]);
    if (!device) {
      return null;
    }

    return {
      device_id: device.id,
      message_type: "soundbox_heartbeat",
      serial_number: soundboxHeartbeatMatch[1],
      is_heartbeat: true
    };
  }

  const soundboxTopicMatch = topic.match(/^soundbox\/([^/]+)\/up$/);
  if (soundboxTopicMatch) {
    const device = await getDeviceBySerialNumber(soundboxTopicMatch[1]);
    if (!device) {
      return null;
    }

    return {
      device_id: device.id,
      message_type: "soundbox_up"
    };
  }

  return null;
}

function parseDeviceTime(value: unknown) {
  if (typeof value !== "string") {
    return new Date().toISOString();
  }

  const match = value.match(/^(\d{4})(\d{2})(\d{2})(\d{2})(\d{2})(\d{2})$/);
  if (!match) {
    return new Date().toISOString();
  }

  // QF100 firmware sends NTP-local WIB time (UTC+7) in YYYYMMDDHHMMSS.
  // Store timestamps in UTC so heartbeat age and last-seen calculations stay correct.
  const parsed = new Date(
    Date.UTC(
      Number(match[1]),
      Number(match[2]) - 1,
      Number(match[3]),
      Number(match[4]) - 7,
      Number(match[5]),
      Number(match[6])
    )
  );
  return Number.isFinite(parsed.getTime()) ? parsed.toISOString() : new Date().toISOString();
}

function numberOrNull(value: unknown) {
  const parsed = Number(value);
  return Number.isFinite(parsed) ? parsed : null;
}

async function recordSoundboxHeartbeat(parsedTopic: ParsedMqttTopic, payload: Record<string, unknown>) {
  const data = payload.data && typeof payload.data === "object" && !Array.isArray(payload.data)
    ? (payload.data as Record<string, unknown>)
    : {};
  const wifiAp = data["wifi-ap"] && typeof data["wifi-ap"] === "object" && !Array.isArray(data["wifi-ap"])
    ? (data["wifi-ap"] as Record<string, unknown>)
    : {};
  const mainCellInfo = data["main-cell-info"] && typeof data["main-cell-info"] === "object" && !Array.isArray(data["main-cell-info"])
    ? (data["main-cell-info"] as Record<string, unknown>)
    : {};
  const timestamp = parseDeviceTime(data.time);
  const networkStrength = numberOrNull(wifiAp.rssi) ?? numberOrNull(mainCellInfo.rssi);
  const batteryLevel = numberOrNull(data["battery-level"]);
  const firmwareVersion = typeof data["fw-version"] === "string" ? data["fw-version"] : undefined;

  await patchDevice(parsedTopic.device_id, {
    last_seen_at: new Date().toISOString(),
    ...(firmwareVersion ? { firmware_version: firmwareVersion } : {})
  });
  await createDeviceHeartbeat({
    device_id: parsedTopic.device_id,
    timestamp,
    firmware_version: firmwareVersion,
    network_strength: networkStrength,
    battery_level: batteryLevel,
    state: "mqtt_heartbeat",
    payload_json: {
      source: "soundbox_mqtt_heartbeat",
      serial_number: parsedTopic.serial_number,
      ...payload
    }
  });
}

function parsePayload(raw: Buffer): Record<string, unknown> {
  const text = raw.toString("utf8");
  try {
    const parsed = JSON.parse(text) as unknown;
    return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? (parsed as Record<string, unknown>) : { raw_text: text };
  } catch (_error) {
    return { raw_text: text };
  }
}

export function startMqttSubscriber() {
  if (started || !status.enabled) {
    return;
  }
  started = true;

  if (!env.MQTT_BROKER_URL) {
    status.last_error = { message: "MQTT_BROKER_URL_MISSING", at: new Date().toISOString() };
    return;
  }

  const clientId = `${env.MQTT_CLIENT_ID}-subscriber`;
  status.client_id = clientId;
  const options: IClientOptions = {
    clientId,
    username: env.MQTT_USERNAME || undefined,
    password: env.MQTT_PASSWORD || undefined,
    connectTimeout: env.MQTT_CONNECT_TIMEOUT_MS,
    reconnectPeriod: 5000,
    clean: true
  };

  const client = mqtt.connect(env.MQTT_BROKER_URL, options);
  clientRef = client;

  client.on("connect", () => {
    status.connected = true;
    status.last_connected_at = new Date().toISOString();
    for (const topic of status.topics) {
      client.subscribe(topic, { qos: 1 }, (error) => {
        if (error) {
          status.last_error = { message: error.message, at: new Date().toISOString() };
        }
      });
    }
  });

  client.on("message", (topic, raw) => {
    status.received_count += 1;
    status.last_message_at = new Date().toISOString();
    parseTopic(topic)
      .then((parsedTopic) => {
        if (!parsedTopic) {
          status.failed_count += 1;
          status.last_error = { message: `UNSUPPORTED_TOPIC:${topic}`, at: new Date().toISOString() };
          return null;
        }

        const payload = parsePayload(raw);
        return createMqttMessage({
          direction: "uplink",
          device_id: parsedTopic.device_id,
          topic,
          message_type: String(payload.message_type || parsedTopic.message_type),
          correlation_id: typeof payload.correlation_id === "string" ? payload.correlation_id : undefined,
          payload_json: payload,
          publish_status: "recorded"
        }).then((message) => {
          if (parsedTopic.is_heartbeat) {
            return recordSoundboxHeartbeat(parsedTopic, payload).then(() => message);
          }
          return message;
        });
      })
      .then((message) => {
        if (message) {
          status.recorded_count += 1;
        }
      })
      .catch((error: unknown) => {
        status.failed_count += 1;
        const message = error instanceof Error ? error.message : "unknown";
        status.last_error = { message, at: new Date().toISOString() };
      });
  });

  client.on("error", (error) => {
    status.last_error = { message: error.message, at: new Date().toISOString() };
  });

  client.on("close", () => {
    status.connected = false;
    status.last_disconnected_at = new Date().toISOString();
  });
}

export function getMqttSubscriberStatus() {
  return {
    ...status,
    connected: status.enabled ? Boolean(clientRef?.connected) : false
  };
}
