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 { LinkplayClient } from './linkplay.classes.client.js';
import { LinkplayConfigFlow } from './linkplay.classes.configflow.js';
import { createLinkplayDiscoveryDescriptor } from './linkplay.discovery.js';
import { LinkplayMapper } from './linkplay.mapper.js';
import type { ILinkplayCommandRequest, ILinkplayConfig } from './linkplay.types.js';

export class LinkplayIntegration extends BaseIntegration<ILinkplayConfig> {
  public readonly domain = 'linkplay';
  public readonly displayName = 'LinkPlay';
  public readonly status = 'control-runtime' as const;
  public readonly discoveryDescriptor = createLinkplayDiscoveryDescriptor();
  public readonly configFlow = new LinkplayConfigFlow();
  public readonly metadata = {
    source: 'home-assistant/core',
    upstreamPath: 'homeassistant/components/linkplay',
    upstreamDomain: 'linkplay',
    integrationType: 'hub',
    iotClass: 'local_polling',
    requirements: ['python-linkplay==0.2.12'],
    dependencies: [],
    afterDependencies: [],
    codeowners: ['@Velleman'],
    configFlow: true,
    documentation: 'https://www.home-assistant.io/integrations/linkplay',
    zeroconf: ['_linkplay._tcp.local.'],
  };

  public async setup(configArg: ILinkplayConfig, contextArg: IIntegrationSetupContext): Promise<IIntegrationRuntime> {
    void contextArg;
    return new LinkplayRuntime(new LinkplayClient(configArg));
  }

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

export class HomeAssistantLinkplayIntegration extends LinkplayIntegration {}

class LinkplayRuntime implements IIntegrationRuntime {
  public domain = 'linkplay';

  constructor(private readonly client: LinkplayClient) {}

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

  public async entities(): Promise<IIntegrationEntity[]> {
    return LinkplayMapper.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 === 'linkplay') {
        return await this.callLinkplayService(requestArg);
      }
      if (requestArg.domain === 'select') {
        return await this.callSelectService(requestArg);
      }
      if (requestArg.domain === 'button') {
        return await this.callButtonService(requestArg);
      }
      return { success: false, error: `Unsupported LinkPlay 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 === 'join' || requestArg.service === 'join_players') {
      return { success: true, data: await this.client.execute({ command: 'join', speakerUuid: await this.speakerUuidFromRequest(requestArg), groupMemberUuids: await this.memberUuidsFromRequest(requestArg) }) };
    }
    if (requestArg.service === 'unjoin' || requestArg.service === 'unjoin_player') {
      return { success: true, data: await this.client.execute({ command: 'unjoin', speakerUuid: await this.speakerUuidFromRequest(requestArg) }) };
    }
    if (requestArg.service === 'group_volume_set') {
      return { success: true, data: await this.client.execute({ command: 'group_volume_set', speakerUuid: await this.speakerUuidFromRequest(requestArg), volumeLevel: this.numberData(requestArg, 'volume_level'), volume: this.numberData(requestArg, 'volume') }) };
    }
    if (requestArg.service === 'group_mute') {
      return { success: true, data: await this.client.execute({ command: 'group_mute', speakerUuid: await this.speakerUuidFromRequest(requestArg), muted: this.booleanData(requestArg, 'is_volume_muted') ?? this.booleanData(requestArg, 'muted') ?? this.booleanData(requestArg, 'mute') }) };
    }

    const command = this.commandFromMediaService(requestArg.service);
    if (!command) {
      return { success: false, error: `Unsupported LinkPlay media_player service: ${requestArg.service}` };
    }
    return { success: true, data: await this.client.execute(await this.commandRequest(command, requestArg)) };
  }

  private async callLinkplayService(requestArg: IServiceCallRequest): Promise<IServiceCallResult> {
    if (requestArg.service === 'play_preset') {
      return { success: true, data: await this.client.execute({ command: 'play_preset', speakerUuid: await this.speakerUuidFromRequest(requestArg), preset: this.numberData(requestArg, 'preset_number') ?? this.numberData(requestArg, 'preset') }) };
    }
    if (requestArg.service === 'timesync' || requestArg.service === 'sync_time') {
      return { success: true, data: await this.client.execute({ command: 'timesync', speakerUuid: await this.speakerUuidFromRequest(requestArg) }) };
    }
    if (requestArg.service === 'restart' || requestArg.service === 'reboot') {
      return { success: true, data: await this.client.execute({ command: 'restart', speakerUuid: await this.speakerUuidFromRequest(requestArg) }) };
    }
    if (requestArg.service === 'set_audio_output_hardware_mode') {
      return { success: true, data: await this.client.execute({ command: 'set_audio_output_hardware_mode', speakerUuid: await this.speakerUuidFromRequest(requestArg), hardwareMode: this.stringData(requestArg, 'hardware_mode') || this.stringData(requestArg, 'option') }) };
    }
    if (requestArg.service === 'command' || requestArg.service === 'raw_command') {
      const rawCommand = this.stringData(requestArg, 'command') || this.stringData(requestArg, 'rawCommand');
      if (!rawCommand) {
        throw new Error('LinkPlay raw command service requires data.command.');
      }
      return { success: true, data: await this.client.execute({ command: 'raw_command', speakerUuid: await this.optionalSpeakerUuidFromRequest(requestArg), rawCommand }) };
    }
    if (requestArg.service === 'join' || requestArg.service === 'join_players' || requestArg.service === 'unjoin' || requestArg.service === 'unjoin_player' || requestArg.service === 'group_volume_set' || requestArg.service === 'group_mute') {
      return this.callMediaPlayerService({ ...requestArg, domain: 'media_player' });
    }
    if (this.commandFromMediaService(requestArg.service)) {
      return this.callMediaPlayerService({ ...requestArg, domain: 'media_player' });
    }
    return { success: false, error: `Unsupported LinkPlay service: ${requestArg.service}` };
  }

  private async callSelectService(requestArg: IServiceCallRequest): Promise<IServiceCallResult> {
    if (requestArg.service !== 'select_option') {
      return { success: false, error: `Unsupported LinkPlay select service: ${requestArg.service}` };
    }
    return { success: true, data: await this.client.execute({ command: 'set_audio_output_hardware_mode', speakerUuid: await this.speakerUuidFromRequest(requestArg), hardwareMode: this.stringData(requestArg, 'option') }) };
  }

  private async callButtonService(requestArg: IServiceCallRequest): Promise<IServiceCallResult> {
    if (requestArg.service === 'press') {
      const button = this.stringData(requestArg, 'button') || requestArg.target.entityId || '';
      const command = button.includes('restart') ? 'restart' : 'timesync';
      return { success: true, data: await this.client.execute({ command, speakerUuid: await this.speakerUuidFromRequest(requestArg) }) };
    }
    return { success: false, error: `Unsupported LinkPlay button service: ${requestArg.service}` };
  }

  private commandFromMediaService(serviceArg: string): ILinkplayCommandRequest['command'] | undefined {
    if (serviceArg === 'media_play' || serviceArg === 'play') {
      return 'play';
    }
    if (serviceArg === 'media_pause' || serviceArg === 'pause') {
      return 'pause';
    }
    if (serviceArg === 'media_stop' || serviceArg === 'stop') {
      return 'stop';
    }
    if (serviceArg === 'media_next_track' || serviceArg === 'next_track' || serviceArg === 'next') {
      return 'next_track';
    }
    if (serviceArg === 'media_previous_track' || serviceArg === 'previous_track' || serviceArg === 'previous') {
      return 'previous_track';
    }
    if (serviceArg === 'volume_set' || serviceArg === 'set_volume') {
      return 'set_volume';
    }
    if (serviceArg === 'volume_mute' || serviceArg === 'mute') {
      return 'mute';
    }
    if (serviceArg === 'select_source' || serviceArg === 'source') {
      return 'select_source';
    }
    if (serviceArg === 'select_sound_mode' || serviceArg === 'set_sound_mode') {
      return 'set_sound_mode';
    }
    if (serviceArg === 'repeat_set' || serviceArg === 'set_repeat') {
      return 'set_repeat';
    }
    if (serviceArg === 'media_seek' || serviceArg === 'seek') {
      return 'seek';
    }
    if (serviceArg === 'play_media') {
      return 'play_media';
    }
    return undefined;
  }

  private async commandRequest(commandArg: ILinkplayCommandRequest['command'], requestArg: IServiceCallRequest): Promise<ILinkplayCommandRequest> {
    const base: ILinkplayCommandRequest = {
      command: commandArg,
      speakerUuid: await this.speakerUuidFromRequest(requestArg),
    };
    if (commandArg === 'set_volume') {
      base.volumeLevel = this.numberData(requestArg, 'volume_level');
      base.volume = this.numberData(requestArg, 'volume');
    }
    if (commandArg === 'mute') {
      base.muted = this.booleanData(requestArg, 'is_volume_muted') ?? this.booleanData(requestArg, 'muted') ?? this.booleanData(requestArg, 'mute');
    }
    if (commandArg === 'select_source') {
      base.source = this.stringData(requestArg, 'source');
    }
    if (commandArg === 'set_sound_mode') {
      base.soundMode = this.stringData(requestArg, 'sound_mode') || this.stringData(requestArg, 'soundMode');
    }
    if (commandArg === 'set_repeat') {
      const repeat = this.stringData(requestArg, 'repeat') || this.stringData(requestArg, 'repeat_mode');
      if (repeat === 'off' || repeat === 'one' || repeat === 'all') {
        base.repeat = repeat;
      }
    }
    if (commandArg === 'seek') {
      base.position = this.numberData(requestArg, 'seek_position') ?? this.numberData(requestArg, 'position');
    }
    if (commandArg === 'play_media') {
      base.mediaId = this.stringData(requestArg, 'media_content_id') || this.stringData(requestArg, 'mediaId') || this.stringData(requestArg, 'uri');
      base.url = this.stringData(requestArg, 'url');
      base.mediaType = this.stringData(requestArg, 'media_content_type') || this.stringData(requestArg, 'mediaType');
    }
    return base;
  }

  private async speakerUuidFromRequest(requestArg: IServiceCallRequest): Promise<string> {
    const speakerUuid = await this.optionalSpeakerUuidFromRequest(requestArg);
    if (speakerUuid) {
      return speakerUuid;
    }
    throw new Error('LinkPlay service call requires data.speaker_uuid or a target LinkPlay media_player entity.');
  }

  private async optionalSpeakerUuidFromRequest(requestArg: IServiceCallRequest): Promise<string | undefined> {
    const direct = this.stringData(requestArg, 'speaker_uuid') || this.stringData(requestArg, 'speakerUuid') || this.stringData(requestArg, 'uuid');
    if (direct) {
      return direct;
    }
    const snapshot = await this.client.getSnapshot();
    if (requestArg.target.entityId) {
      const entitySpeakerUuid = LinkplayMapper.entitySpeakerUuid(snapshot, requestArg.target.entityId);
      if (entitySpeakerUuid) {
        return entitySpeakerUuid;
      }
    }
    if (requestArg.target.deviceId) {
      const speaker = snapshot.speakers.find((speakerArg) => LinkplayMapper.speakerDeviceId(speakerArg) === requestArg.target.deviceId);
      if (speaker) {
        return speaker.uuid;
      }
    }
    return snapshot.speakers.length === 1 ? snapshot.speakers[0].uuid : undefined;
  }

  private async memberUuidsFromRequest(requestArg: IServiceCallRequest): Promise<string[]> {
    const direct = this.stringArrayData(requestArg, 'speaker_uuids') || this.stringArrayData(requestArg, 'speakerUuids') || this.stringArrayData(requestArg, 'member_uuids') || this.stringArrayData(requestArg, 'memberUuids');
    if (direct?.length) {
      return direct;
    }
    const members = this.stringArrayData(requestArg, 'group_members') || this.stringArrayData(requestArg, 'groupMembers');
    if (!members?.length) {
      throw new Error('LinkPlay join service requires data.group_members or data.speaker_uuids.');
    }
    const snapshot = await this.client.getSnapshot();
    return members.map((memberArg) => LinkplayMapper.entitySpeakerUuid(snapshot, memberArg) || memberArg);
  }

  private stringData(requestArg: IServiceCallRequest, keyArg: string): string | undefined {
    const value = requestArg.data?.[keyArg];
    return typeof value === 'string' && value ? value : undefined;
  }

  private numberData(requestArg: IServiceCallRequest, keyArg: string): number | undefined {
    const value = requestArg.data?.[keyArg];
    return typeof value === 'number' && Number.isFinite(value) ? value : undefined;
  }

  private booleanData(requestArg: IServiceCallRequest, keyArg: string): boolean | undefined {
    const value = requestArg.data?.[keyArg];
    return typeof value === 'boolean' ? value : undefined;
  }

  private stringArrayData(requestArg: IServiceCallRequest, keyArg: string): string[] | undefined {
    const value = requestArg.data?.[keyArg];
    if (typeof value === 'string') {
      return [value];
    }
    return Array.isArray(value) && value.every((itemArg) => typeof itemArg === 'string') ? value : undefined;
  }
}
