import type { ISynologySrmCommand, ISynologySrmCommandResult, ISynologySrmConfig, ISynologySrmEvent, ISynologySrmSnapshot } from './synology_srm.types.js';
import { SynologySrmMapper } from './synology_srm.mapper.js';

type TSynologySrmEventHandler = (eventArg: ISynologySrmEvent) => void;

export class SynologySrmClient {
  private currentSnapshot?: ISynologySrmSnapshot;
  private readonly eventHandlers = new Set<TSynologySrmEventHandler>();

  constructor(private readonly config: ISynologySrmConfig) {}

  public async getSnapshot(): Promise<ISynologySrmSnapshot> {
    if (this.config.nativeClient) {
      this.currentSnapshot = this.normalizeSnapshot(await this.config.nativeClient.getSnapshot(), 'provider');
      return this.cloneSnapshot(this.currentSnapshot);
    }

    if (this.config.snapshotProvider) {
      const provided = await this.config.snapshotProvider();
      if (provided) {
        this.currentSnapshot = this.normalizeSnapshot(provided, 'provider');
        return this.cloneSnapshot(this.currentSnapshot);
      }
    }

    if (!this.currentSnapshot) {
      this.currentSnapshot = SynologySrmMapper.toSnapshot(this.config, this.config.connected ?? this.config.online ?? this.hasManualData());
      if (!this.hasManualData()) {
        this.currentSnapshot = {
          ...this.currentSnapshot,
          connected: false,
          error: this.config.host
            ? 'Synology SRM live HTTP/API access is not implemented by this dependency-free TypeScript port. Provide nativeClient, snapshotProvider, or snapshot/manual data.'
            : 'Synology SRM setup requires a router host plus nativeClient/snapshotProvider, or snapshot/manual data.',
        };
      }
    }
    return this.cloneSnapshot(this.currentSnapshot);
  }

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

  public async refresh(): Promise<ISynologySrmCommandResult> {
    try {
      this.currentSnapshot = undefined;
      const snapshot = await this.getSnapshot();
      const success = snapshot.connected || this.hasManualData() || Boolean(this.config.nativeClient || this.config.snapshotProvider);
      this.emit({ type: success ? 'snapshot_refreshed' : 'refresh_failed', snapshot, error: success ? undefined : snapshot.error, timestamp: Date.now() });
      return success ? { success: true, data: snapshot } : { success: false, error: snapshot.error, data: snapshot };
    } catch (errorArg) {
      const error = errorArg instanceof Error ? errorArg.message : String(errorArg);
      const snapshot = SynologySrmMapper.toSnapshot({ ...this.config, connected: false, snapshot: this.currentSnapshot }, false);
      this.currentSnapshot = { ...snapshot, error };
      this.emit({ type: 'refresh_failed', snapshot: this.currentSnapshot, error, timestamp: Date.now() });
      return { success: false, error, data: this.cloneSnapshot(this.currentSnapshot) };
    }
  }

  public async sendCommand(commandArg: ISynologySrmCommand): Promise<ISynologySrmCommandResult> {
    this.emit({ type: 'command_mapped', command: commandArg, deviceId: commandArg.deviceId, entityId: commandArg.entityId, timestamp: Date.now() });

    const executor = this.config.commandExecutor || this.config.nativeClient?.executeCommand?.bind(this.config.nativeClient);
    if (!executor) {
      const result: ISynologySrmCommandResult = {
        success: false,
        error: this.unsupportedCommandMessage(commandArg),
        data: { command: commandArg },
      };
      this.emit({ type: 'command_failed', command: commandArg, data: result, deviceId: commandArg.deviceId, entityId: commandArg.entityId, timestamp: Date.now() });
      return result;
    }

    try {
      const result = this.commandResult(await executor(commandArg), commandArg);
      this.emit({ type: result.success ? 'command_executed' : 'command_failed', command: commandArg, data: result, deviceId: commandArg.deviceId, entityId: commandArg.entityId, timestamp: Date.now() });
      return result;
    } catch (errorArg) {
      const result: ISynologySrmCommandResult = { success: false, error: errorArg instanceof Error ? errorArg.message : String(errorArg), data: { command: commandArg } };
      this.emit({ type: 'command_failed', command: commandArg, data: result, deviceId: commandArg.deviceId, entityId: commandArg.entityId, timestamp: Date.now() });
      return result;
    }
  }

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

  private normalizeSnapshot(snapshotArg: ISynologySrmSnapshot, sourceArg: ISynologySrmSnapshot['source']): ISynologySrmSnapshot {
    const normalized = SynologySrmMapper.toSnapshot({ ...this.config, snapshot: this.cloneSnapshot(snapshotArg) }, snapshotArg.connected);
    return { ...normalized, source: snapshotArg.source || sourceArg };
  }

  private hasManualData(): boolean {
    return Boolean(
      this.config.snapshot
      || this.config.router
      || this.config.system
      || this.config.network
      || this.config.interfaces?.length
      || this.config.clients?.length
      || this.config.devices?.length
      || this.config.traffic
      || this.config.sensors
      || this.config.switches
      || this.config.actions?.length
      || this.config.manualEntries?.length
    );
  }

  private commandResult(resultArg: unknown, commandArg: ISynologySrmCommand): ISynologySrmCommandResult {
    if (this.isCommandResult(resultArg)) {
      return resultArg;
    }
    return { success: true, data: resultArg ?? { command: commandArg } };
  }

  private isCommandResult(valueArg: unknown): valueArg is ISynologySrmCommandResult {
    return Boolean(valueArg && typeof valueArg === 'object' && 'success' in valueArg);
  }

  private unsupportedCommandMessage(commandArg: ISynologySrmCommand): string {
    const action = commandArg.action.replace(/_/g, ' ');
    return `Synology SRM live ${action} command execution is not faked by this native TypeScript port. Provide commandExecutor or nativeClient.executeCommand for live router, client, or switch actions.`;
  }

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

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