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 { MqttClient } from './mqtt.classes.client.js';
import { MqttConfigFlow } from './mqtt.classes.configflow.js';
import { createMqttDiscoveryDescriptor } from './mqtt.discovery.js';
import { MqttMapper } from './mqtt.mapper.js';
import type { IMqttConfig, IMqttPublishOptions } from './mqtt.types.js';

export class MqttIntegration extends BaseIntegration<IMqttConfig> {
  public readonly domain = 'mqtt';
  public readonly displayName = 'MQTT';
  public readonly status = 'control-runtime' as const;
  public readonly discoveryDescriptor = createMqttDiscoveryDescriptor();
  public readonly configFlow = new MqttConfigFlow();
  public readonly metadata = {
    source: 'home-assistant/core',
    upstreamPath: 'homeassistant/components/mqtt',
    upstreamDomain: 'mqtt',
    integrationType: 'service',
    iotClass: 'local_push',
    qualityScale: 'platinum',
  };

  public async setup(configArg: IMqttConfig, contextArg: IIntegrationSetupContext): Promise<IIntegrationRuntime> {
    void contextArg;
    return new MqttRuntime(new MqttClient(configArg));
  }

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

export class HomeAssistantMqttIntegration extends MqttIntegration {}

class MqttRuntime implements IIntegrationRuntime {
  public domain = 'mqtt';

  constructor(private readonly client: MqttClient) {}

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

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

  public async subscribe(handlerArg: (eventArg: IIntegrationEvent) => void): Promise<() => Promise<void>> {
    return this.client.subscribe('#', (messageArg) => {
      handlerArg({
        type: 'state_changed',
        integrationDomain: 'mqtt',
        data: messageArg,
        timestamp: messageArg.timestamp || Date.now(),
      });
    });
  }

  public async callService(requestArg: IServiceCallRequest): Promise<IServiceCallResult> {
    if (requestArg.domain === 'mqtt') {
      if (requestArg.service !== 'publish') {
        return { success: false, error: `Unsupported MQTT service: ${requestArg.service}` };
      }
      const publishOptions = this.publishOptionsFromRequest(requestArg);
      if (!publishOptions) {
        return { success: false, error: 'MQTT publish requires data.topic.' };
      }
      await this.client.publish(publishOptions);
      return { success: true };
    }

    const publishOptions = MqttMapper.commandForService(await this.client.getSnapshot(), requestArg);
    if (!publishOptions) {
      return { success: false, error: `MQTT entity service ${requestArg.domain}.${requestArg.service} has no command topic mapping.` };
    }
    await this.client.publish(publishOptions);
    return { success: true };
  }

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

  private publishOptionsFromRequest(requestArg: IServiceCallRequest): IMqttPublishOptions | undefined {
    const topic = requestArg.data?.topic;
    if (typeof topic !== 'string' || !topic) {
      return undefined;
    }
    const qos = requestArg.data?.qos;
    return {
      topic,
      payload: requestArg.data?.payload as IMqttPublishOptions['payload'],
      qos: qos === 1 || qos === 2 ? qos : 0,
      retain: requestArg.data?.retain === true,
    };
  }
}
