import { BrotherMapper } from './brother.mapper.js';
import type { IBrotherClientLike, IBrotherConfig, IBrotherRawData, IBrotherRefreshResult, IBrotherSensors, IBrotherSnapshot } from './brother.types.js';

export class BrotherClient {
  private currentSnapshot?: IBrotherSnapshot;

  constructor(private readonly config: IBrotherConfig) {}

  public async getSnapshot(): Promise<IBrotherSnapshot> {
    if (this.config.snapshot) {
      this.currentSnapshot = BrotherMapper.toSnapshot({ config: this.config, source: 'snapshot', online: this.config.snapshot.online });
      return this.cloneSnapshot(this.currentSnapshot);
    }

    if (this.config.client) {
      try {
        this.currentSnapshot = await this.snapshotFromClient(this.config.client);
      } catch (errorArg) {
        this.currentSnapshot = this.offlineSnapshot(this.errorMessage(errorArg));
      }
      return this.cloneSnapshot(this.currentSnapshot);
    }

    if (this.hasManualData()) {
      this.currentSnapshot = BrotherMapper.toSnapshot({
        config: this.config,
        rawData: this.config.rawData,
        sensors: this.config.sensors,
        online: this.config.online ?? true,
        source: 'manual',
      });
      return this.cloneSnapshot(this.currentSnapshot);
    }

    this.currentSnapshot = this.offlineSnapshot(
      this.config.host
        ? 'Native Brother SNMP transport is not implemented; provide snapshot, sensors, rawData, or an injected client.'
        : 'Brother setup requires config.host with an injected client, or snapshot/sensors/rawData manual data.'
    );
    return this.cloneSnapshot(this.currentSnapshot);
  }

  public async refresh(): Promise<IBrotherRefreshResult> {
    const snapshot = await this.getSnapshot();
    const success = snapshot.online && snapshot.source !== 'runtime' && !snapshot.error;
    return { success, snapshot, error: success ? undefined : snapshot.error };
  }

  public async ping(): Promise<boolean> {
    const snapshot = await this.getSnapshot();
    return snapshot.online && snapshot.source !== 'runtime' && !snapshot.error;
  }

  public hasUsableSource(): boolean {
    return Boolean(this.config.snapshot || this.config.client || this.hasManualData());
  }

  public async destroy(): Promise<void> {}

  private async snapshotFromClient(clientArg: IBrotherClientLike): Promise<IBrotherSnapshot> {
    if (clientArg.getSnapshot) {
      const result = await clientArg.getSnapshot();
      if (isBrotherSnapshot(result)) {
        return BrotherMapper.toSnapshot({ config: { ...this.config, snapshot: result }, source: 'client', online: result.online });
      }
      return BrotherMapper.toSnapshot({
        config: this.config,
        printer: result.printer,
        rawData: result.rawData,
        sensors: result.sensors,
        online: result.online ?? true,
        source: result.source || 'client',
        error: result.error,
      });
    }

    const rawData = clientArg.getRawData ? await clientArg.getRawData() : undefined;
    const sensors = clientArg.getSensors ? await clientArg.getSensors() : clientArg.asyncUpdate ? await clientArg.asyncUpdate() : undefined;
    if (!rawData && !sensors) {
      throw new Error('Brother client must expose getSnapshot(), getRawData(), getSensors(), or asyncUpdate().');
    }
    return BrotherMapper.toSnapshot({ config: this.config, rawData, sensors, online: true, source: 'client' });
  }

  private offlineSnapshot(errorArg: string): IBrotherSnapshot {
    return BrotherMapper.toSnapshot({
      config: this.config,
      online: false,
      source: 'runtime',
      error: errorArg,
    });
  }

  private hasManualData(): boolean {
    return Boolean(this.config.rawData || this.config.sensors);
  }

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

  private cloneSnapshot(snapshotArg: IBrotherSnapshot): IBrotherSnapshot {
    return JSON.parse(JSON.stringify(snapshotArg)) as IBrotherSnapshot;
  }
}

const isBrotherSnapshot = (valueArg: unknown): valueArg is IBrotherSnapshot => {
  return Boolean(valueArg && typeof valueArg === 'object' && 'printer' in valueArg && 'sensors' in valueArg && 'online' in valueArg);
};
