import type * as shxInterfaces from '@smarthome.exchange/interfaces';
import { BaseIntegration } from '../../core/classes.baseintegration.js';
import type { IIntegrationEntity, IIntegrationEvent, IIntegrationRuntime, IIntegrationSetupContext, IServiceCallRequest, IServiceCallResult } from '../../core/types.js';
import { KnxClient } from './knx.classes.client.js';
import { KnxConfigFlow } from './knx.classes.configflow.js';
import { createKnxDiscoveryDescriptor } from './knx.discovery.js';
import { KnxMapper } from './knx.mapper.js';
import type { IKnxClientCommand, IKnxConfig } from './knx.types.js';

export class KnxIntegration extends BaseIntegration<IKnxConfig> {
  public readonly domain = 'knx';
  public readonly displayName = 'KNX';
  public readonly status = 'control-runtime' as const;
  public readonly discoveryDescriptor = createKnxDiscoveryDescriptor();
  public readonly configFlow = new KnxConfigFlow();
  public readonly metadata = {
    source: 'home-assistant/core',
    upstreamPath: 'homeassistant/components/knx',
    upstreamDomain: 'knx',
    integrationType: 'hub',
    iotClass: 'local_push',
    qualityScale: 'platinum',
    requirements: [
      'xknx==3.15.0',
      'xknxproject==3.9.0',
      'knx-frontend==2026.4.30.60856',
    ],
    dependencies: [
      'file_upload',
      'http',
      'websocket_api',
    ],
    afterDependencies: [
      'panel_custom',
    ],
    codeowners: [
      '@Julius2342',
      '@farmio',
      '@marvin-w',
    ],
  };

  public async setup(configArg: IKnxConfig, contextArg: IIntegrationSetupContext): Promise<IIntegrationRuntime> {
    void contextArg;
    return new KnxRuntime(new KnxClient(configArg));
  }

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

export class HomeAssistantKnxIntegration extends KnxIntegration {}

class KnxRuntime implements IIntegrationRuntime {
  public domain = 'knx';

  constructor(private readonly client: KnxClient) {}

  public async devices(): Promise<shxInterfaces.data.IDeviceDefinition[]> {
    return KnxMapper.toDevices(await this.client.getSnapshot());
  }

  public async entities(): Promise<IIntegrationEntity[]> {
    return KnxMapper.toEntities(await this.client.getSnapshot());
  }

  public async subscribe(handlerArg: (eventArg: IIntegrationEvent) => void): Promise<() => Promise<void>> {
    const unsubscribe = this.client.onEvent((eventArg) => {
      handlerArg(KnxMapper.toIntegrationEvent(eventArg));
    });
    await this.client.getSnapshot();
    return async () => unsubscribe();
  }

  public async callService(requestArg: IServiceCallRequest): Promise<IServiceCallResult> {
    const command = requestArg.domain === 'knx'
      ? this.commandFromKnxService(requestArg)
      : KnxMapper.commandForService(await this.client.getSnapshot(), requestArg);
    if (!command) {
      return { success: false, error: `Unsupported KNX service: ${requestArg.domain}.${requestArg.service}` };
    }
    return this.client.sendCommand(command);
  }

  public async destroy(): Promise<void> {
    await this.client.destroy();
  }

  private commandFromKnxService(requestArg: IServiceCallRequest): IKnxClientCommand | undefined {
    if (requestArg.service === 'send' || requestArg.service === 'group_write') {
      const addresses = this.addressesFromData(requestArg.data?.address ?? requestArg.data?.addresses);
      const dpt = this.stringValue(requestArg.data?.type ?? requestArg.data?.dpt);
      return KnxMapper.groupWriteCommand(addresses, requestArg.data?.payload, dpt, requestArg.data?.response === true);
    }
    if (requestArg.service === 'read' || requestArg.service === 'group_read') {
      return KnxMapper.groupReadCommand(this.addressesFromData(requestArg.data?.address ?? requestArg.data?.addresses));
    }
    return undefined;
  }

  private addressesFromData(valueArg: unknown): string[] {
    if (typeof valueArg === 'string' && valueArg.trim()) {
      return [valueArg.trim()];
    }
    if (Array.isArray(valueArg)) {
      return valueArg.filter((itemArg): itemArg is string => typeof itemArg === 'string' && Boolean(itemArg.trim())).map((itemArg) => itemArg.trim());
    }
    return [];
  }

  private stringValue(valueArg: unknown): string | undefined {
    return typeof valueArg === 'string' && valueArg.trim() ? valueArg.trim() : undefined;
  }
}
