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 { MatterClient } from './matter.classes.client.js';
import { MatterConfigFlow } from './matter.classes.configflow.js';
import { createMatterDiscoveryDescriptor } from './matter.discovery.js';
import { MatterMapper } from './matter.mapper.js';
import type { IMatterConfig, IMatterServerCommand, TMatterNodeId } from './matter.types.js';

export class MatterIntegration extends BaseIntegration<IMatterConfig> {
  public readonly domain = 'matter';
  public readonly displayName = 'Matter';
  public readonly status = 'control-runtime' as const;
  public readonly discoveryDescriptor = createMatterDiscoveryDescriptor();
  public readonly configFlow = new MatterConfigFlow();
  public readonly metadata = {
    source: 'home-assistant/core',
    domain: 'matter',
    name: 'Matter',
    upstreamPath: 'homeassistant/components/matter',
    upstreamDomain: 'matter',
    configFlow: true,
    integrationType: 'hub',
    iotClass: 'local_push',
    requirements: ['matter-python-client==0.6.0'],
    dependencies: ['websocket_api'],
    afterDependencies: ['hassio'],
    codeowners: ['@home-assistant/matter'],
    documentation: 'https://www.home-assistant.io/integrations/matter',
    zeroconf: ['_matter._tcp.local.', '_matterc._udp.local.'],
  };

  public async setup(configArg: IMatterConfig, contextArg: IIntegrationSetupContext): Promise<IIntegrationRuntime> {
    void contextArg;
    return new MatterRuntime(new MatterClient(configArg));
  }

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

export class HomeAssistantMatterIntegration extends MatterIntegration {}

class MatterRuntime implements IIntegrationRuntime {
  public domain = 'matter';

  constructor(private readonly client: MatterClient) {}

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

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

  public async subscribe(handlerArg: (eventArg: IIntegrationEvent) => void): Promise<() => Promise<void>> {
    const unsubscribe = this.client.onEvent((eventArg) => {
      handlerArg({
        type: this.integrationEventType(eventArg.event),
        integrationDomain: 'matter',
        deviceId: this.deviceIdFromEvent(eventArg.data),
        data: eventArg,
        timestamp: Date.now(),
      });
    });
    await this.client.getSnapshot();
    return async () => unsubscribe();
  }

  public async callService(requestArg: IServiceCallRequest): Promise<IServiceCallResult> {
    const command = requestArg.domain === 'matter'
      ? this.commandFromMatterService(requestArg)
      : MatterMapper.commandForService(await this.client.getSnapshot(), requestArg);
    if (!command) {
      return { success: false, error: `Unsupported Matter service: ${requestArg.domain}.${requestArg.service}` };
    }
    const data = await this.client.sendCommand(command);
    return { success: true, data };
  }

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

  private commandFromMatterService(requestArg: IServiceCallRequest): IMatterServerCommand | undefined {
    if (requestArg.service === 'commission_with_code') {
      const code = this.stringValue(requestArg.data?.code);
      return code ? { command: 'commission_with_code', args: { code, network_only: requestArg.data?.network_only === true || requestArg.data?.networkOnly === true }, timeoutMs: 600000 } : undefined;
    }
    if (requestArg.service === 'commission_on_network') {
      const setupPinCode = this.numberValue(requestArg.data?.setup_pin_code ?? requestArg.data?.setupPinCode);
      if (setupPinCode === undefined) {
        return undefined;
      }
      return {
        command: 'commission_on_network',
        args: {
          setup_pin_code: setupPinCode,
          filter_type: this.numberValue(requestArg.data?.filter_type ?? requestArg.data?.filterType),
          filter: this.numberValue(requestArg.data?.filter),
          ip_addr: this.stringValue(requestArg.data?.ip_addr ?? requestArg.data?.ipAddr),
        },
        timeoutMs: 600000,
      };
    }
    if (requestArg.service === 'open_commissioning_window') {
      const nodeId = this.nodeIdFromRequest(requestArg);
      return nodeId === undefined ? undefined : {
        command: 'open_commissioning_window',
        args: {
          node_id: nodeId,
          timeout: this.numberValue(requestArg.data?.timeout),
          iteration: this.numberValue(requestArg.data?.iteration),
          option: this.numberValue(requestArg.data?.option),
          discriminator: this.numberValue(requestArg.data?.discriminator),
        },
        timeoutMs: 600000,
      };
    }
    if (requestArg.service === 'remove_node') {
      const nodeId = this.nodeIdFromRequest(requestArg);
      return nodeId === undefined ? undefined : { command: 'remove_node', args: { node_id: nodeId }, timeoutMs: 600000 };
    }
    if (requestArg.service === 'interview_node') {
      const nodeId = this.nodeIdFromRequest(requestArg);
      return nodeId === undefined ? undefined : { command: 'interview_node', args: { node_id: nodeId }, timeoutMs: 600000 };
    }
    if (requestArg.service === 'ping_node') {
      const nodeId = this.nodeIdFromRequest(requestArg);
      return nodeId === undefined ? undefined : { command: 'ping_node', args: { node_id: nodeId, attempts: this.numberValue(requestArg.data?.attempts) } };
    }
    if (requestArg.service === 'set_default_fabric_label') {
      const label = requestArg.data?.label;
      return label === null || typeof label === 'string' ? { command: 'set_default_fabric_label', requireSchema: 11, args: { label } } : undefined;
    }
    return undefined;
  }

  private integrationEventType(eventArg: string): IIntegrationEvent['type'] {
    if (eventArg === 'node_added' || eventArg === 'endpoint_added') {
      return 'device_added';
    }
    if (eventArg === 'node_removed' || eventArg === 'endpoint_removed') {
      return 'device_removed';
    }
    if (eventArg === 'node_updated') {
      return 'availability_changed';
    }
    if (eventArg === 'attribute_updated' || eventArg === 'node_event') {
      return 'state_changed';
    }
    return eventArg === 'server_shutdown' ? 'error' : 'state_changed';
  }

  private deviceIdFromEvent(dataArg: unknown): string | undefined {
    if (Array.isArray(dataArg)) {
      return `matter.node.${this.slug(String(dataArg[0]))}`;
    }
    if (this.isRecord(dataArg)) {
      const nodeId = dataArg.node_id ?? dataArg.nodeId;
      const endpointId = this.numberValue(dataArg.endpoint_id ?? dataArg.endpointId);
      if (nodeId !== undefined) {
        return endpointId === undefined ? `matter.node.${this.slug(String(nodeId))}` : `matter.node.${this.slug(String(nodeId))}.endpoint.${endpointId}`;
      }
    }
    if (typeof dataArg === 'number' || typeof dataArg === 'bigint' || typeof dataArg === 'string') {
      return `matter.node.${this.slug(String(dataArg))}`;
    }
    return undefined;
  }

  private nodeIdFromRequest(requestArg: IServiceCallRequest): TMatterNodeId | undefined {
    const value = requestArg.data?.node_id ?? requestArg.data?.nodeId;
    if (typeof value === 'number' || typeof value === 'bigint' || typeof value === 'string') {
      return value;
    }
    const deviceId = requestArg.target.deviceId;
    const match = deviceId?.match(/^matter\.node\.([^.]+)/);
    return match?.[1];
  }

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

  private numberValue(valueArg: unknown): number | undefined {
    if (typeof valueArg === 'number' && Number.isFinite(valueArg)) {
      return valueArg;
    }
    if (typeof valueArg === 'string' && valueArg.trim() && Number.isFinite(Number(valueArg))) {
      return Number(valueArg);
    }
    return undefined;
  }

  private isRecord(valueArg: unknown): valueArg is Record<string, unknown> {
    return typeof valueArg === 'object' && valueArg !== null && !Array.isArray(valueArg);
  }

  private slug(valueArg: string): string {
    return valueArg.toLowerCase().replace(/[^a-z0-9]+/g, '_').replace(/^_+|_+$/g, '') || 'matter';
  }
}
