import { BaseStream } from './Stream';

export interface APRSMessage {
  topic: string;
  receivedAt: Date;
  raw: string;
  radioName?: string;
}

interface APRSJsonEnvelope {
  raw?: string;
  payload?: string;
  payloadBase64?: string;
  rawBase64?: string;
  Raw?: string;
  Payload?: string;
  Time?: string;
  time?: string;
  timestamp?: string;
}

const fromBase64 = (value: string): string => {
  return atob(value);
};

const pickString = (obj: Record<string, unknown>, keys: string[]): string | undefined => {
  for (const key of keys) {
    const value = obj[key];
    if (typeof value === 'string' && value.trim().length > 0) {
      return value;
    }
  }
  return undefined;
};

export class APRSStream extends BaseStream {
  constructor(autoConnect = false) {
    super({}, autoConnect);
  }

  protected decodeMessage(topic: string, payload: Uint8Array): APRSMessage {
    const text = new TextDecoder().decode(payload).trim();
    const radioName = this.extractRadioNameFromTopic(topic);

    if (!text.startsWith('{')) {
      return {
        topic,
        receivedAt: new Date(),
        raw: text,
        radioName,
      };
    }

    const envelope = JSON.parse(text) as APRSJsonEnvelope;
    const record = envelope as unknown as Record<string, unknown>;

    const raw = this.extractRawFrame(record);
    const receivedAt = this.extractReceivedAt(record);

    return {
      topic,
      receivedAt,
      raw,
      radioName,
    };
  }

  private extractRawFrame(envelope: Record<string, unknown>): string {
    const rawFrame = pickString(envelope, ['raw', 'payload']);
    if (rawFrame && rawFrame.includes('>')) {
      return rawFrame;
    }

    const maybeBase64 = pickString(envelope, ['Raw', 'payloadBase64', 'rawBase64', 'Payload']);
    if (maybeBase64) {
      const decoded = fromBase64(maybeBase64);
      if (decoded.includes('>')) {
        return decoded;
      }
    }

    if (rawFrame) {
      return rawFrame;
    }

    throw new Error('APRSStream: invalid payload envelope, no APRS frame found');
  }

  private extractReceivedAt(envelope: Record<string, unknown>): Date {
    const timeValue = pickString(envelope, ['time', 'Time', 'timestamp']);
    if (!timeValue) {
      return new Date();
    }

    const parsed = new Date(timeValue);
    if (Number.isNaN(parsed.getTime())) {
      return new Date();
    }

    return parsed;
  }

  private extractRadioNameFromTopic(topic: string): string | undefined {
    const parts = topic.split('/');
    if (parts.length >= 3 && parts[0] === 'aprs' && parts[1] === 'packet') {
      try {
        return atob(parts[2]);
      } catch (error) {
        console.warn('Failed to decode radio name from topic:', topic, error);
      }
    }

    return undefined;
  }
}

export default APRSStream;
