import * as plugins from '../../plugins.js';
import { DsmrTelegramParser } from './dsmr.parser.js';
import type { IDsmrConfig, IDsmrEvent, IDsmrRefreshResult, IDsmrSnapshot, IDsmrStatusSnapshot } from './dsmr.types.js';
import { dsmrDefaultNetworkPort, dsmrDefaultTimeoutMs } from './dsmr.types.js';

type TDsmrEventHandler = (eventArg: IDsmrEvent) => void;

export class DsmrClient {
  private currentSnapshot?: IDsmrSnapshot;
  private readonly eventHandlers = new Set<TDsmrEventHandler>();

  constructor(private readonly config: IDsmrConfig) {}

  public async getSnapshot(): Promise<IDsmrSnapshot> {
    if (!this.currentSnapshot) {
      this.currentSnapshot = await this.initialSnapshot();
    }
    return this.cloneSnapshot(this.currentSnapshot);
  }

  public onEvent(handlerArg: TDsmrEventHandler): () => void {
    this.eventHandlers.add(handlerArg);
    return () => this.eventHandlers.delete(handlerArg);
  }

  public async refresh(): Promise<IDsmrRefreshResult> {
    try {
      const provided = await this.providedTelegramSnapshot();
      if (provided) {
        this.currentSnapshot = provided;
        this.emit({ type: 'snapshot_refreshed', snapshot: this.cloneSnapshot(provided), timestamp: Date.now() });
        return { success: true, snapshot: this.cloneSnapshot(provided), data: { source: provided.source } };
      }

      if (this.canReadNetwork()) {
        const telegram = await this.readNetworkTelegram();
        const snapshot = DsmrTelegramParser.parseTelegram(telegram, { config: this.config, source: 'network', connected: true });
        this.currentSnapshot = snapshot;
        this.emit({ type: 'telegram_received', snapshot: this.cloneSnapshot(snapshot), timestamp: Date.now() });
        return { success: true, snapshot: this.cloneSnapshot(snapshot), data: { source: 'network' } };
      }

      const connection = DsmrTelegramParser.connectionInfo(this.config);
      const reason = connection.connectionType === 'serial'
        ? 'Native DSMR serial reading requires a telegramProvider or snapshot; no serial transport dependency is configured.'
        : 'No DSMR telegram source is configured. Provide a snapshot, telegram, telegramProvider, or enable liveRead for a network endpoint.';
      const snapshot = await this.getSnapshot();
      this.emit({ type: 'refresh_failed', snapshot, error: reason, timestamp: Date.now() });
      return { success: false, snapshot, error: reason };
    } catch (errorArg) {
      const snapshot = await this.getSnapshot().catch(() => DsmrTelegramParser.emptySnapshot(this.config));
      const error = errorArg instanceof Error ? errorArg.message : String(errorArg);
      this.currentSnapshot = { ...snapshot, connected: false, updatedAt: new Date().toISOString() };
      this.emit({ type: 'refresh_failed', snapshot: this.cloneSnapshot(this.currentSnapshot), error, timestamp: Date.now() });
      return { success: false, snapshot: this.cloneSnapshot(this.currentSnapshot), error };
    }
  }

  public async readNetworkTelegram(): Promise<string> {
    const connection = DsmrTelegramParser.connectionInfo(this.config);
    if (connection.connectionType !== 'network' || !connection.host) {
      throw new Error('DSMR network telegram reading requires config.host and network connection type.');
    }
    const port = typeof connection.port === 'number' ? connection.port : dsmrDefaultNetworkPort;
    const timeoutMs = this.config.timeoutMs || dsmrDefaultTimeoutMs;
    return await new Promise<string>((resolve, reject) => {
      const socket = plugins.net.createConnection({ host: connection.host, port });
      let buffer = '';
      let settled = false;
      const timeout = setTimeout(() => finish(new Error(`DSMR network telegram read timed out after ${timeoutMs}ms.`)), timeoutMs);
      const finish = (errorArg?: Error, telegramArg?: string) => {
        if (settled) {
          return;
        }
        settled = true;
        clearTimeout(timeout);
        socket.removeAllListeners();
        socket.destroy();
        if (errorArg) {
          reject(errorArg);
          return;
        }
        resolve(telegramArg || '');
      };
      const processBuffer = () => {
        const telegram = this.extractTelegram(buffer);
        if (telegram) {
          finish(undefined, telegram);
        }
      };
      socket.on('data', (dataArg) => {
        buffer += dataArg.toString('utf8');
        processBuffer();
      });
      socket.on('error', (errorArg) => finish(errorArg));
      socket.on('close', () => {
        processBuffer();
        if (!settled) {
          finish(new Error('DSMR network connection closed before a complete telegram was received.'));
        }
      });
    });
  }

  public async destroy(): Promise<void> {
    this.eventHandlers.clear();
  }

  private async initialSnapshot(): Promise<IDsmrSnapshot> {
    if (this.config.snapshot) {
      return DsmrTelegramParser.normalizeSnapshot(this.cloneSnapshot(this.config.snapshot), this.config);
    }
    if (this.config.status) {
      return DsmrTelegramParser.snapshotFromStatus(this.config.status, this.config);
    }
    if (typeof this.config.telegram === 'string') {
      return DsmrTelegramParser.parseTelegram(this.config.telegram, { config: this.config, source: 'telegram' });
    }
    if (this.isSnapshot(this.config.telegram)) {
      return DsmrTelegramParser.normalizeSnapshot(this.cloneSnapshot(this.config.telegram), this.config);
    }
    if (Array.isArray(this.config.telegrams) && this.config.telegrams.length) {
      return DsmrTelegramParser.parseTelegram(this.config.telegrams[this.config.telegrams.length - 1], { config: this.config, source: 'telegram' });
    }
    return DsmrTelegramParser.emptySnapshot(this.config, this.config.connected ?? false);
  }

  private async providedTelegramSnapshot(): Promise<IDsmrSnapshot | undefined> {
    if (this.config.telegramProvider) {
      const provided = await this.config.telegramProvider();
      return this.snapshotFromProvided(provided);
    }
    if (this.config.snapshot || this.config.status || this.config.telegram || this.config.telegrams?.length) {
      return await this.initialSnapshot();
    }
    return undefined;
  }

  private snapshotFromProvided(providedArg: string | IDsmrSnapshot | IDsmrStatusSnapshot | undefined): IDsmrSnapshot | undefined {
    if (typeof providedArg === 'string') {
      return DsmrTelegramParser.parseTelegram(providedArg, { config: this.config, source: 'telegram' });
    }
    if (this.isSnapshot(providedArg)) {
      return DsmrTelegramParser.normalizeSnapshot(this.cloneSnapshot(providedArg), this.config);
    }
    if (providedArg && typeof providedArg === 'object') {
      return DsmrTelegramParser.snapshotFromStatus(providedArg as IDsmrStatusSnapshot, this.config);
    }
    return undefined;
  }

  private canReadNetwork(): boolean {
    const connection = DsmrTelegramParser.connectionInfo(this.config);
    return this.config.liveRead === true && connection.connectionType === 'network' && Boolean(connection.host);
  }

  private extractTelegram(bufferArg: string): string | undefined {
    const start = bufferArg.indexOf('/');
    if (start < 0) {
      return undefined;
    }
    const body = bufferArg.slice(start);
    const checksumMatch = body.match(/![0-9a-fA-F]{0,4}/);
    if (!checksumMatch || checksumMatch.index === undefined) {
      return undefined;
    }
    return body.slice(0, checksumMatch.index + checksumMatch[0].length);
  }

  private emit(eventArg: IDsmrEvent): void {
    for (const handler of this.eventHandlers) {
      handler(eventArg);
    }
  }

  private isSnapshot(valueArg: unknown): valueArg is IDsmrSnapshot {
    return Boolean(valueArg && typeof valueArg === 'object' && 'meter' in valueArg && Array.isArray((valueArg as IDsmrSnapshot).sensors));
  }

  private cloneSnapshot<T extends IDsmrSnapshot>(snapshotArg: T): T {
    return JSON.parse(JSON.stringify(snapshotArg)) as T;
  }
}
