import * as plugins from '../../plugins.js';
import { SimpleLocalClient, type IServiceCallResult, type ISimpleLocalSnapshot } from '../../core/index.js';
import { LandisgyrHeatMeterMapper } from './landisgyr_heat_meter.mapper.js';
import type { ILandisgyrHeatMeterConfig, ILandisgyrHeatMeterSnapshot } from './landisgyr_heat_meter.types.js';
import { landisgyrHeatMeterProfile } from './landisgyr_heat_meter.types.js';

export class LandisgyrHeatMeterClient extends SimpleLocalClient<ILandisgyrHeatMeterConfig> {
  private cachedSnapshot?: ILandisgyrHeatMeterSnapshot;
  private readonly landisgyrConfig: ILandisgyrHeatMeterConfig;

  constructor(configArg: ILandisgyrHeatMeterConfig) {
    super(landisgyrHeatMeterProfile, configArg);
    this.landisgyrConfig = configArg;
  }

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

    if (!forceRefreshArg && this.landisgyrConfig.snapshot) {
      this.cachedSnapshot = LandisgyrHeatMeterMapper.toSnapshot({ config: this.landisgyrConfig, source: 'snapshot', online: this.landisgyrConfig.snapshot.online });
      return LandisgyrHeatMeterMapper.clone(this.cachedSnapshot);
    }

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

    if (this.landisgyrConfig.filePath) {
      try {
        const rawData = await plugins.fs.readFile(this.landisgyrConfig.filePath, 'utf8');
        this.cachedSnapshot = LandisgyrHeatMeterMapper.toSnapshot({ config: this.landisgyrConfig, rawData, online: true, source: 'file' });
      } catch (errorArg) {
        this.cachedSnapshot = this.createOfflineSnapshot(this.errorMessage(errorArg));
      }
      return LandisgyrHeatMeterMapper.clone(this.cachedSnapshot);
    }

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

    this.cachedSnapshot = this.createOfflineSnapshot('Landis+Gyr Heat Meter snapshots require config.filePath, an injected client, snapshot, entities, or rawData. Direct USB/IR serial polling requires ultraheat-api/pyserial and is not implemented in this package.');
    return LandisgyrHeatMeterMapper.clone(this.cachedSnapshot);
  }

  public 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 async destroy(): Promise<void> {
    await this.landisgyrConfig.client?.destroy?.();
  }

  private async snapshotFromInjectedClient(): Promise<ILandisgyrHeatMeterSnapshot> {
    const client = this.landisgyrConfig.client;
    if (!client) {
      throw new Error('No Landis+Gyr Heat Meter client configured.');
    }
    if (client.getSnapshot) {
      const result = await client.getSnapshot();
      if (this.isSimpleSnapshot(result)) {
        return LandisgyrHeatMeterMapper.toSnapshot({ config: { ...this.landisgyrConfig, snapshot: result }, source: 'client', online: result.online });
      }
      return LandisgyrHeatMeterMapper.toSnapshot({ config: this.landisgyrConfig, rawData: result, online: true, source: 'client' });
    }
    if (client.getState) {
      return LandisgyrHeatMeterMapper.toSnapshot({ config: this.landisgyrConfig, rawData: await client.getState(), online: true, source: 'client' });
    }
    if (client.getRawData) {
      return LandisgyrHeatMeterMapper.toSnapshot({ config: this.landisgyrConfig, rawData: await client.getRawData(), online: true, source: 'client' });
    }
    throw new Error('Landis+Gyr Heat Meter client must expose getSnapshot(), getState(), or getRawData().');
  }

  private createOfflineSnapshot(errorArg: string): ILandisgyrHeatMeterSnapshot {
    return LandisgyrHeatMeterMapper.toSnapshot({ config: this.landisgyrConfig, rawData: this.landisgyrConfig.rawData, online: false, source: 'runtime', error: errorArg });
  }

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

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