import * as plugins from '../../plugins.js';
import { SimpleLocalClient } from '../../core/index.js';
import { LocalIpMapper } from './local_ip.mapper.js';
import type { ILocalIpConfig, ILocalIpSnapshot } from './local_ip.types.js';
import { localIpProfile } from './local_ip.types.js';

export class LocalIpClient extends SimpleLocalClient<ILocalIpConfig> {
  constructor(private readonly configArg: ILocalIpConfig) {
    super(localIpProfile, configArg);
  }

  public async getSnapshot(forceRefreshArg = false): Promise<ILocalIpSnapshot> {
    if (this.shouldProbeSourceIp()) {
      try {
        return LocalIpMapper.toSnapshot({ config: this.configArg, rawData: await this.probeSourceIp(), online: true, source: 'tcp' });
      } catch (errorArg) {
        return LocalIpMapper.toSnapshot({ config: this.configArg, online: false, source: 'runtime', error: errorMessage(errorArg) });
      }
    }

    const snapshot = await super.getSnapshot(forceRefreshArg);
    if (snapshot.rawData === undefined && snapshot.entities.length) {
      return LocalIpMapper.toSnapshot({ config: { ...this.configArg, snapshot }, online: snapshot.online, source: snapshot.source, error: snapshot.error });
    }

    return LocalIpMapper.toSnapshot({ config: this.configArg, rawData: snapshot.rawData, online: snapshot.online, source: snapshot.source, error: snapshot.error });
  }

  private shouldProbeSourceIp(): boolean {
    return Boolean(this.targetHost() && !this.configArg.snapshot && this.configArg.rawData === undefined && this.configArg.rawText === undefined && !this.configArg.entities?.length && !this.configArg.client && !this.configArg.snapshotProvider);
  }

  private async probeSourceIp(): Promise<Record<string, unknown>> {
    const host = this.targetHost();
    const port = this.targetPort();
    if (!host || !port) {
      throw new Error('Local IP snapshots require config.targetHost/config.targetPort or config.host/config.port for a reachable TCP source-address probe.');
    }

    const timeoutMs = this.configArg.timeoutMs || 5000;
    return new Promise<Record<string, unknown>>((resolve, reject) => {
      const socket = plugins.net.createConnection({ host, port });
      let settled = false;

      const finish = (errorArg?: Error) => {
        if (settled) {
          return;
        }
        settled = true;
        socket.removeAllListeners();
        socket.destroy();
        if (errorArg) {
          reject(errorArg);
        }
      };

      socket.setTimeout(timeoutMs, () => finish(new Error(`Local IP TCP probe to ${host}:${port} timed out after ${timeoutMs}ms.`)));
      socket.on('connect', () => {
        const localAddress = socket.localAddress;
        if (!localAddress) {
          finish(new Error(`Local IP TCP probe to ${host}:${port} connected without a local address.`));
          return;
        }
        const result = {
          address: localAddress,
          localAddress,
          localPort: socket.localPort,
          family: localAddress.includes(':') ? 'IPv6' : 'IPv4',
          targetHost: host,
          targetPort: port,
          protocol: 'tcp',
        };
        if (!settled) {
          settled = true;
          socket.removeAllListeners();
          socket.end();
          socket.destroy();
          resolve(result);
        }
      });
      socket.on('error', (errorArg) => finish(errorArg));
      socket.on('close', () => finish(new Error(`Local IP TCP probe to ${host}:${port} closed before connecting.`)));
    });
  }

  private targetHost(): string | undefined {
    const metadata = this.configArg.metadata;
    return stringValue(this.configArg.targetHost) || stringValue(this.configArg.target_host) || stringValue(this.configArg.host) || stringValue(metadata?.targetHost) || stringValue(metadata?.target_host);
  }

  private targetPort(): number | undefined {
    const metadata = this.configArg.metadata;
    return numberValue(this.configArg.targetPort) || numberValue(this.configArg.target_port) || numberValue(this.configArg.port) || numberValue(metadata?.targetPort) || numberValue(metadata?.target_port);
  }
}

const numberValue = (valueArg: unknown): number | undefined => {
  const value = typeof valueArg === 'number' ? valueArg : typeof valueArg === 'string' && valueArg.trim() ? Number(valueArg) : undefined;
  return value !== undefined && Number.isFinite(value) ? Math.round(value) : undefined;
};

const stringValue = (valueArg: unknown): string | undefined => typeof valueArg === 'string' && valueArg.trim() ? valueArg.trim() : undefined;

const errorMessage = (errorArg: unknown): string => errorArg instanceof Error ? errorArg.message : String(errorArg);
