import type {
  ITplinkClientCommand,
  ITplinkCommandResult,
  ITplinkConfig,
  ITplinkEvent,
  ITplinkSnapshot,
} from './tplink.types.js';
import { TplinkMapper } from './tplink.mapper.js';

type TTplinkEventHandler = (eventArg: ITplinkEvent) => void;

export class TplinkClient {
  private readonly events: ITplinkEvent[] = [];
  private readonly eventHandlers = new Set<TTplinkEventHandler>();

  constructor(private readonly config: ITplinkConfig) {}

  public async getSnapshot(): Promise<ITplinkSnapshot> {
    return TplinkMapper.toSnapshot(this.config, undefined, this.events);
  }

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

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

    if (this.config.commandExecutor) {
      const result = this.commandResult(await this.config.commandExecutor(commandArg), commandArg);
      this.emit({
        type: result.success ? 'command_executed' : 'command_failed',
        command: commandArg,
        data: result,
        deviceId: commandArg.deviceId,
        entityId: commandArg.entityId,
        uniqueId: commandArg.uniqueId,
        timestamp: Date.now(),
      });
      return result;
    }

    const result: ITplinkCommandResult = {
      success: false,
      error: this.unsupportedLiveControlMessage(),
      data: { command: commandArg },
    };
    this.emit({
      type: 'command_failed',
      command: commandArg,
      data: result,
      deviceId: commandArg.deviceId,
      entityId: commandArg.entityId,
      uniqueId: commandArg.uniqueId,
      timestamp: Date.now(),
    });
    return result;
  }

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

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

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

  private isCommandResult(valueArg: unknown): valueArg is ITplinkCommandResult {
    return typeof valueArg === 'object' && valueArg !== null && 'success' in valueArg;
  }

  private unsupportedLiveControlMessage(): string {
    return 'TP-Link Kasa/Tapo live local writes require full python-kasa-equivalent protocol selection and encrypted transports (legacy IOT XOR plus SMART AES/KLAP). This dependency-free TypeScript port is snapshot/manual unless commandExecutor is provided.';
  }
}
