import type * as shxInterfaces from '@smarthome.exchange/interfaces';
import { BaseIntegration } from '../../core/classes.baseintegration.js';
import type { IIntegrationEntity, IIntegrationRuntime, IIntegrationSetupContext, IServiceCallRequest, IServiceCallResult } from '../../core/types.js';
import { MpdClient } from './mpd.classes.client.js';
import { MpdConfigFlow } from './mpd.classes.configflow.js';
import { createMpdDiscoveryDescriptor } from './mpd.discovery.js';
import { MpdMapper } from './mpd.mapper.js';
import type { IMpdConfig, IMpdOutput, IMpdSnapshot } from './mpd.types.js';

export class MpdIntegration extends BaseIntegration<IMpdConfig> {
  public readonly domain = 'mpd';
  public readonly displayName = 'Music Player Daemon (MPD)';
  public readonly status = 'control-runtime' as const;
  public readonly discoveryDescriptor = createMpdDiscoveryDescriptor();
  public readonly configFlow = new MpdConfigFlow();
  public readonly metadata = {
    source: 'home-assistant/core',
    upstreamPath: 'homeassistant/components/mpd',
    upstreamDomain: 'mpd',
    integrationType: 'device',
    iotClass: 'local_polling',
    requirements: ['python-mpd2==3.1.1'],
    dependencies: [],
    afterDependencies: [],
    codeowners: [],
    configFlow: true,
    documentation: 'https://www.home-assistant.io/integrations/mpd',
  };

  public async setup(configArg: IMpdConfig, contextArg: IIntegrationSetupContext): Promise<IIntegrationRuntime> {
    void contextArg;
    return new MpdRuntime(new MpdClient(configArg));
  }

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

export class HomeAssistantMpdIntegration extends MpdIntegration {}

class MpdRuntime implements IIntegrationRuntime {
  public domain = 'mpd';

  constructor(private readonly client: MpdClient) {}

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

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

  public async callService(requestArg: IServiceCallRequest): Promise<IServiceCallResult> {
    try {
      if (requestArg.domain === 'media_player') {
        return await this.callMediaPlayerService(requestArg);
      }
      if (requestArg.domain === 'mpd') {
        return await this.callMpdService(requestArg);
      }
      return { success: false, error: `Unsupported MPD service domain: ${requestArg.domain}` };
    } catch (errorArg) {
      return { success: false, error: errorArg instanceof Error ? errorArg.message : String(errorArg) };
    }
  }

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

  private async callMediaPlayerService(requestArg: IServiceCallRequest): Promise<IServiceCallResult> {
    if (requestArg.service === 'play' || requestArg.service === 'media_play') {
      await this.client.play();
      return { success: true };
    }
    if (requestArg.service === 'pause' || requestArg.service === 'media_pause') {
      await this.client.pause(true);
      return { success: true };
    }
    if (requestArg.service === 'play_pause' || requestArg.service === 'media_play_pause') {
      const snapshot = await this.client.getSnapshot();
      await this.client.pause(snapshot.status.state === 'play');
      return { success: true };
    }
    if (requestArg.service === 'stop' || requestArg.service === 'media_stop') {
      await this.client.stop();
      return { success: true };
    }
    if (requestArg.service === 'next' || requestArg.service === 'next_track' || requestArg.service === 'media_next_track') {
      await this.client.next();
      return { success: true };
    }
    if (requestArg.service === 'previous' || requestArg.service === 'previous_track' || requestArg.service === 'media_previous_track') {
      await this.client.previous();
      return { success: true };
    }
    if (requestArg.service === 'volume_set' || requestArg.service === 'set_volume' || requestArg.service === 'volume') {
      await this.client.setVolumeLevel(this.numberValue(requestArg.data?.volume_level ?? requestArg.data?.volume, 'MPD volume control requires data.volume_level or data.volume.'));
      return { success: true };
    }
    if (requestArg.service === 'select_source' || requestArg.service === 'source') {
      await this.client.selectSource(this.stringValue(requestArg.data?.source ?? requestArg.data?.playlist, 'MPD source control requires data.source or data.playlist.'));
      return { success: true };
    }
    if (requestArg.service === 'play_media') {
      await this.client.playMedia(this.stringValue(requestArg.data?.media_content_id ?? requestArg.data?.uri, 'MPD play_media requires data.media_content_id or data.uri.'));
      return { success: true };
    }
    if (requestArg.service === 'enable_output' || requestArg.service === 'disable_output' || requestArg.service === 'toggle_output' || requestArg.service === 'set_output' || requestArg.service === 'output') {
      await this.callOutputService(requestArg);
      return { success: true };
    }
    return { success: false, error: `Unsupported MPD media_player service: ${requestArg.service}` };
  }

  private async callMpdService(requestArg: IServiceCallRequest): Promise<IServiceCallResult> {
    if (requestArg.service === 'snapshot') {
      return { success: true, data: await this.client.snapshot() };
    }
    if (requestArg.service === 'restore') {
      const snapshot = requestArg.data?.snapshot as IMpdSnapshot | undefined;
      await this.client.restore(snapshot);
      return { success: true };
    }
    if (requestArg.service === 'command') {
      const command = this.stringValue(requestArg.data?.command, 'MPD command service requires data.command.');
      const args = this.commandArgs(requestArg.data?.args);
      return { success: true, data: await this.client.command(command, args) };
    }
    if (requestArg.service === 'play' || requestArg.service === 'pause' || requestArg.service === 'stop' || requestArg.service === 'next' || requestArg.service === 'previous' || requestArg.service === 'volume_set' || requestArg.service === 'set_volume' || requestArg.service === 'volume' || requestArg.service === 'select_source' || requestArg.service === 'source') {
      return this.callMediaPlayerService({ ...requestArg, domain: 'media_player' });
    }
    if (requestArg.service === 'enable_output' || requestArg.service === 'disable_output' || requestArg.service === 'toggle_output' || requestArg.service === 'set_output' || requestArg.service === 'output') {
      await this.callOutputService(requestArg);
      return { success: true };
    }
    return { success: false, error: `Unsupported MPD service: ${requestArg.service}` };
  }

  private async callOutputService(requestArg: IServiceCallRequest): Promise<void> {
    const outputId = await this.outputIdFromRequest(requestArg);
    if (requestArg.service === 'toggle_output' || requestArg.service === 'output' && requestArg.data?.enabled === undefined) {
      await this.client.toggleOutput(outputId);
      return;
    }
    const enabled = requestArg.service === 'enable_output'
      ? true
      : requestArg.service === 'disable_output'
        ? false
        : this.booleanValue(requestArg.data?.enabled, 'MPD set_output/output requires data.enabled.');
    await this.client.setOutput(outputId, enabled);
  }

  private async outputIdFromRequest(requestArg: IServiceCallRequest): Promise<string | number> {
    const direct = requestArg.data?.output_id ?? requestArg.data?.outputId ?? requestArg.data?.id;
    if (typeof direct === 'string' || typeof direct === 'number') {
      return direct;
    }
    const targetId = requestArg.target.entityId || requestArg.target.deviceId;
    if (!targetId) {
      throw new Error('MPD output control requires data.output_id or a target output switch entity.');
    }
    const snapshot = await this.client.getSnapshot();
    const entity = MpdMapper.toEntities(snapshot).find((entityArg) => entityArg.id === targetId || entityArg.deviceId === targetId);
    const entityOutputId = entity?.attributes?.mpdOutputId;
    if (typeof entityOutputId === 'string' || typeof entityOutputId === 'number') {
      return entityOutputId;
    }
    const output = snapshot.outputs.find((outputArg) => MpdMapper.outputDeviceId(snapshot, outputArg) === targetId || outputEntityId(snapshot, outputArg) === targetId);
    if (!output) {
      throw new Error(`MPD output target was not found: ${targetId}`);
    }
    return output.outputid;
  }

  private commandArgs(valueArg: unknown): Array<string | number | boolean> {
    if (valueArg === undefined) {
      return [];
    }
    const values = Array.isArray(valueArg) ? valueArg : [valueArg];
    if (values.some((itemArg) => typeof itemArg !== 'string' && typeof itemArg !== 'number' && typeof itemArg !== 'boolean')) {
      throw new Error('MPD command args must be strings, numbers, or booleans.');
    }
    return values as Array<string | number | boolean>;
  }

  private numberValue(valueArg: unknown, errorArg: string): number {
    if (typeof valueArg !== 'number' || !Number.isFinite(valueArg)) {
      throw new Error(errorArg);
    }
    return valueArg;
  }

  private booleanValue(valueArg: unknown, errorArg: string): boolean {
    if (typeof valueArg !== 'boolean') {
      throw new Error(errorArg);
    }
    return valueArg;
  }

  private stringValue(valueArg: unknown, errorArg: string): string {
    if (typeof valueArg !== 'string' || !valueArg) {
      throw new Error(errorArg);
    }
    return valueArg;
  }
}

const outputEntityId = (snapshotArg: IMpdSnapshot, outputArg: IMpdOutput): string => {
  const serverName = snapshotArg.server.name || snapshotArg.server.host || 'Music Player Daemon';
  return `switch.${MpdMapper.slug(serverName)}_${MpdMapper.slug(outputArg.outputname)}_mpd_output`;
};
