import { SimpleLocalClient, SimpleLocalMapper, type IServiceCallRequest, type IServiceCallResult, type ISimpleLocalSnapshot, type TSimpleLocalRawData } from '../../core/index.js';
import type { IPingConfig } from './ping.types.js';
import { pingProfile } from './ping.types.js';

export class PingClient extends SimpleLocalClient<IPingConfig> {
  private cachedSnapshot?: ISimpleLocalSnapshot;

  constructor(private readonly configArg: IPingConfig) {
    super(pingProfile, configArg);
  }

  public override async getSnapshot(forceRefreshArg = false): Promise<ISimpleLocalSnapshot> {
    if (!forceRefreshArg && this.cachedSnapshot) {
      return SimpleLocalMapper.clone(this.cachedSnapshot);
    }

    if (this.configArg.client) {
      try {
        this.cachedSnapshot = await this.snapshotFromInjectedClient();
      } catch (errorArg) {
        this.cachedSnapshot = this.createOfflineSnapshot(errorMessage(errorArg));
      }
      return SimpleLocalMapper.clone(this.cachedSnapshot);
    }

    if (this.configArg.snapshotProvider) {
      try {
        const value = await this.configArg.snapshotProvider();
        if (value !== undefined) {
          this.cachedSnapshot = SimpleLocalMapper.toSnapshot({ profile: pingProfile, config: this.configArg, rawData: value, source: 'provider', online: true });
          return SimpleLocalMapper.clone(this.cachedSnapshot);
        }
      } catch (errorArg) {
        this.cachedSnapshot = this.createOfflineSnapshot(errorMessage(errorArg));
        return SimpleLocalMapper.clone(this.cachedSnapshot);
      }
    }

    const rawData = this.configArg.rawData ?? this.configArg.rawText;
    if (rawData !== undefined || this.configArg.entities) {
      this.cachedSnapshot = SimpleLocalMapper.toSnapshot({ profile: pingProfile, config: this.configArg, rawData, source: 'manual', online: this.configArg.online ?? true });
      return SimpleLocalMapper.clone(this.cachedSnapshot);
    }

    if (this.configArg.snapshot) {
      this.cachedSnapshot = SimpleLocalMapper.toSnapshot({ profile: pingProfile, config: this.configArg, rawData: this.configArg.snapshot, source: 'snapshot', online: this.configArg.snapshot.online });
      return SimpleLocalMapper.clone(this.cachedSnapshot);
    }

    this.cachedSnapshot = this.createOfflineSnapshot('Ping live polling is not implemented: Home Assistant uses ICMP through icmplib/raw sockets or the OS ping subprocess, not an HTTP/TCP/TLS/file protocol available through existing integration dependencies.');
    return SimpleLocalMapper.clone(this.cachedSnapshot);
  }

  public override async refresh(): Promise<IServiceCallResult> {
    this.cachedSnapshot = undefined;
    const snapshot = await this.getSnapshot(true);
    const success = snapshot.online && !snapshot.error;
    return { success, error: success ? undefined : snapshot.error, data: snapshot };
  }

  public override async execute(requestArg: IServiceCallRequest): Promise<IServiceCallResult> {
    if (this.configArg.client?.execute || this.configArg.commandExecutor) {
      return super.execute(requestArg);
    }
    return {
      success: false,
      error: `Ping service ${requestArg.service} requires an injected client.execute() or commandExecutor; native ICMP polling is blocked because HA uses icmplib/raw sockets or the OS ping subprocess.`,
    };
  }

  private async snapshotFromInjectedClient(): Promise<ISimpleLocalSnapshot> {
    const client = this.configArg.client;
    if (!client) {
      throw new Error('No Ping client configured.');
    }
    if (client.getSnapshot) {
      return this.toSnapshot(await client.getSnapshot(), 'client');
    }
    if (client.getState) {
      return this.toSnapshot(await client.getState(), 'client');
    }
    if (client.getRawData) {
      return this.toSnapshot(await client.getRawData(), 'client');
    }
    throw new Error('Ping client must expose getSnapshot(), getState(), or getRawData().');
  }

  private toSnapshot(valueArg: ISimpleLocalSnapshot | TSimpleLocalRawData, sourceArg: 'client' | 'provider'): ISimpleLocalSnapshot {
    return SimpleLocalMapper.toSnapshot({ profile: pingProfile, config: this.configArg, rawData: valueArg, source: sourceArg, online: isSnapshot(valueArg) ? valueArg.online : true });
  }

  private createOfflineSnapshot(errorArg: string): ISimpleLocalSnapshot {
    return SimpleLocalMapper.toSnapshot({ profile: pingProfile, config: this.configArg, online: false, source: 'runtime', error: errorArg });
  }
}

const isSnapshot = (valueArg: unknown): valueArg is ISimpleLocalSnapshot => {
  return Boolean(valueArg && typeof valueArg === 'object' && 'device' in valueArg && 'entities' in valueArg);
};

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