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 { HeosClient } from './heos.classes.client.js';
import { HeosConfigFlow } from './heos.classes.configflow.js';
import { createHeosDiscoveryDescriptor } from './heos.discovery.js';
import { HeosMapper } from './heos.mapper.js';
import type { IHeosCommandRequest, IHeosConfig, THeosCommand } from './heos.types.js';

export class HeosIntegration extends BaseIntegration<IHeosConfig> {
  public readonly domain = 'heos';
  public readonly displayName = 'Denon HEOS';
  public readonly status = 'control-runtime' as const;
  public readonly discoveryDescriptor = createHeosDiscoveryDescriptor();
  public readonly configFlow = new HeosConfigFlow();
  public readonly metadata = {
    source: 'home-assistant/core',
    upstreamPath: 'homeassistant/components/heos',
    upstreamDomain: 'heos',
    integrationType: 'hub',
    iotClass: 'local_push',
    qualityScale: 'platinum',
    requirements: ['pyheos==1.0.6'],
    dependencies: [],
    afterDependencies: [],
    codeowners: ['@andrewsayre'],
    configFlow: true,
    documentation: 'https://www.home-assistant.io/integrations/heos',
  };

  public async setup(configArg: IHeosConfig, contextArg: IIntegrationSetupContext): Promise<IIntegrationRuntime> {
    void contextArg;
    return new HeosRuntime(new HeosClient(configArg));
  }

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

export class HomeAssistantHeosIntegration extends HeosIntegration {}

class HeosRuntime implements IIntegrationRuntime {
  public domain = 'heos';

  constructor(private readonly client: HeosClient) {}

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

  public async entities(): Promise<IIntegrationEntity[]> {
    return HeosMapper.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 === 'heos') {
        return await this.callHeosService(requestArg);
      }
      return { success: false, error: `Unsupported HEOS 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') {
      const playerIds = await this.joinPlayerIdsFromRequest(requestArg);
      return { success: true, data: await this.client.execute({ command: 'set_group', playerIds }) };
    }
    if (requestArg.service === 'unjoin' || requestArg.service === 'unjoin_player') {
      return { success: true, data: await this.client.execute({ command: 'unjoin', playerId: await this.playerIdFromRequest(requestArg) }) };
    }
    if (requestArg.service === 'group_volume_set') {
      return { success: true, data: await this.client.execute({ command: 'group_volume_set', groupId: await this.groupIdFromRequest(requestArg), volumeLevel: this.numberData(requestArg, 'volume_level') }) };
    }
    if (requestArg.service === 'group_volume_up') {
      return { success: true, data: await this.client.execute({ command: 'group_volume_up', groupId: await this.groupIdFromRequest(requestArg), step: this.numberData(requestArg, 'step') }) };
    }
    if (requestArg.service === 'group_volume_down') {
      return { success: true, data: await this.client.execute({ command: 'group_volume_down', groupId: await this.groupIdFromRequest(requestArg), step: this.numberData(requestArg, 'step') }) };
    }

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

  private async callHeosService(requestArg: IServiceCallRequest): Promise<IServiceCallResult> {
    if (requestArg.service === 'group_volume_set' || requestArg.service === 'group_volume_up' || requestArg.service === 'group_volume_down') {
      return this.callMediaPlayerService({ ...requestArg, domain: 'media_player' });
    }
    if (requestArg.service === 'join' || requestArg.service === 'join_players' || requestArg.service === 'unjoin' || requestArg.service === 'unjoin_player') {
      return this.callMediaPlayerService({ ...requestArg, domain: 'media_player' });
    }
    return { success: false, error: `Unsupported HEOS service: ${requestArg.service}` };
  }

  private commandFromMediaService(serviceArg: string): THeosCommand | 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_previous_track' || serviceArg === 'previous_track') {
      return 'previous_track';
    }
    if (serviceArg === 'media_next_track' || serviceArg === 'next_track') {
      return 'next_track';
    }
    if (serviceArg === 'volume_up') {
      return 'volume_up';
    }
    if (serviceArg === 'volume_down') {
      return 'volume_down';
    }
    if (serviceArg === 'volume_set' || serviceArg === 'set_volume') {
      return 'set_volume';
    }
    if (serviceArg === 'volume_mute' || serviceArg === 'mute') {
      return 'mute';
    }
    if (serviceArg === 'select_source' || serviceArg === 'select_input') {
      return 'select_source';
    }
    if (serviceArg === 'play_media') {
      return 'play_media';
    }
    return undefined;
  }

  private async commandRequest(commandArg: THeosCommand, requestArg: IServiceCallRequest): Promise<IHeosCommandRequest> {
    const base: IHeosCommandRequest = {
      command: commandArg,
      playerId: await this.playerIdFromRequest(requestArg),
      step: this.numberData(requestArg, 'step'),
    };
    if (commandArg === 'set_volume') {
      base.volumeLevel = this.numberData(requestArg, 'volume_level');
      base.volume = this.numberData(requestArg, 'volume');
    }
    if (commandArg === 'mute') {
      base.muted = this.boolData(requestArg, 'is_volume_muted') ?? this.boolData(requestArg, 'mute') ?? this.boolData(requestArg, 'muted');
    }
    if (commandArg === 'select_source') {
      base.source = this.stringData(requestArg, 'source') || this.stringData(requestArg, 'input');
    }
    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 playerIdFromRequest(requestArg: IServiceCallRequest): Promise<number> {
    const direct = this.numberData(requestArg, 'player_id') ?? this.numberData(requestArg, 'playerId') ?? this.numberData(requestArg, 'pid');
    if (typeof direct === 'number') {
      return direct;
    }
    const snapshot = await this.client.getSnapshot();
    if (requestArg.target.entityId) {
      const entityPlayerId = HeosMapper.entityPlayerId(snapshot, requestArg.target.entityId);
      if (typeof entityPlayerId === 'number') {
        return entityPlayerId;
      }
    }
    if (requestArg.target.deviceId) {
      const player = snapshot.players.find((playerArg) => HeosMapper.playerDeviceId(playerArg) === requestArg.target.deviceId);
      if (player) {
        return player.playerId;
      }
    }
    if (snapshot.players.length === 1) {
      return snapshot.players[0].playerId;
    }
    throw new Error('HEOS service call requires data.player_id or a target HEOS media_player entity.');
  }

  private async groupIdFromRequest(requestArg: IServiceCallRequest): Promise<number> {
    const direct = this.numberData(requestArg, 'group_id') ?? this.numberData(requestArg, 'groupId') ?? this.numberData(requestArg, 'gid');
    if (typeof direct === 'number') {
      return direct;
    }
    const playerId = await this.playerIdFromRequest(requestArg);
    const snapshot = await this.client.getSnapshot();
    const player = snapshot.players.find((playerArg) => playerArg.playerId === playerId);
    if (typeof player?.groupId === 'number') {
      return player.groupId;
    }
    throw new Error('HEOS group volume service requires a grouped player or data.group_id.');
  }

  private async joinPlayerIdsFromRequest(requestArg: IServiceCallRequest): Promise<number[]> {
    const leaderId = await this.playerIdFromRequest(requestArg);
    const directIds = this.numberArrayData(requestArg, 'player_ids') || this.numberArrayData(requestArg, 'playerIds');
    if (directIds?.length) {
      return [leaderId, ...directIds.filter((playerIdArg) => playerIdArg !== leaderId)];
    }
    const members = this.stringArrayData(requestArg, 'group_members') || this.stringArrayData(requestArg, 'groupMembers');
    if (!members?.length) {
      throw new Error('HEOS join service requires data.group_members or data.player_ids.');
    }
    const snapshot = await this.client.getSnapshot();
    const memberIds = members.map((entityIdArg) => HeosMapper.entityPlayerId(snapshot, entityIdArg)).filter((playerIdArg): playerIdArg is number => typeof playerIdArg === 'number');
    if (memberIds.length !== members.length) {
      throw new Error('HEOS join service could not resolve all group member entity IDs.');
    }
    return [leaderId, ...memberIds.filter((playerIdArg) => playerIdArg !== leaderId)];
  }

  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 boolData(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;
  }

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