import type { ITradfriCommand, ITradfriCommandResult, ITradfriConfig, ITradfriEvent, ITradfriGateway, ITradfriManualEntry, ITradfriSnapshot } from './tradfri.types.js';
import { tradfriDefaultCoapDtlsPort } from './tradfri.types.js';

type TTradfriEventHandler = (eventArg: ITradfriEvent) => void;

export class TradfriClient {
  private readonly events: ITradfriEvent[] = [];
  private readonly eventHandlers = new Set<TTradfriEventHandler>();

  constructor(private readonly config: ITradfriConfig) {}

  public async getSnapshot(): Promise<ITradfriSnapshot> {
    const source = this.config.snapshot;
    const manualEntry = this.config.manualEntries?.[0];
    const gateway = this.gatewayFromConfig(source, manualEntry);
    return {
      host: this.config.host || source?.host || manualEntry?.host,
      port: this.config.port || source?.port || manualEntry?.port || tradfriDefaultCoapDtlsPort,
      connected: source?.connected ?? false,
      gateway,
      devices: [
        ...(source?.devices || []),
        ...(this.config.devices || []),
      ],
      groups: [
        ...(source?.groups || []),
        ...(this.config.groups || []),
      ],
      states: source?.states || [],
      events: [
        ...(source?.events || []),
        ...this.events,
      ],
      updatedAt: source?.updatedAt,
      raw: source?.raw,
    };
  }

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

  public async sendCommand(commandArg: ITradfriCommand): Promise<ITradfriCommandResult> {
    let result: ITradfriCommandResult;
    if (this.config.commandExecutor) {
      result = this.commandResult(await this.config.commandExecutor(commandArg), commandArg);
    } else {
      result = {
        success: false,
        error: this.unsupportedLiveControlMessage(),
        data: { command: commandArg },
      };
    }
    this.emit({
      type: result.success ? 'command_mapped' : 'command_failed',
      command: commandArg,
      data: result,
      timestamp: Date.now(),
      deviceId: commandArg.deviceId,
      entityId: commandArg.entityId,
    });
    return result;
  }

  public async connectLive(): Promise<void> {
    throw new Error(this.unsupportedLiveControlMessage());
  }

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

  private gatewayFromConfig(sourceArg?: ITradfriSnapshot, manualEntryArg?: ITradfriManualEntry): ITradfriGateway {
    const discoveryRecord = this.config.discoveryRecords?.[0];
    const host = this.config.host || sourceArg?.host || manualEntryArg?.host || discoveryRecord?.host;
    const configuredGateway = this.config.gateway || sourceArg?.gateway;
    return {
      ...configuredGateway,
      id: configuredGateway?.id || this.config.gatewayId || manualEntryArg?.gatewayId || manualEntryArg?.gateway_id || discoveryRecord?.gatewayId || discoveryRecord?.gateway_id || discoveryRecord?.id || host || 'configured',
      name: configuredGateway?.name || manualEntryArg?.name || discoveryRecord?.name || 'IKEA TRADFRI Gateway',
      model: configuredGateway?.model || manualEntryArg?.model || discoveryRecord?.model || 'E1526',
    };
  }

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

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

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

  private unsupportedLiveControlMessage(): string {
    if (this.config.identity && (this.config.psk || this.config.key)) {
      return 'IKEA Tradfri live writes require CoAP over DTLS with PSK authentication. This dependency-free port maps snapshot/manual state only unless a commandExecutor is supplied; the mapped command was not sent.';
    }
    if (this.config.securityCode) {
      return 'IKEA Tradfri pairing requires generating a PSK over CoAP/DTLS from the gateway security code. This dependency-free port does not perform DTLS pairing; the mapped command was not sent.';
    }
    return 'IKEA Tradfri live writes require CoAP over DTLS, which is not implemented in this dependency-free port. Supply snapshot data or a commandExecutor to handle mapped commands.';
  }
}
