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 { BluesoundClient } from './bluesound.classes.client.js';
import { BluesoundConfigFlow } from './bluesound.classes.configflow.js';
import { createBluesoundDiscoveryDescriptor } from './bluesound.discovery.js';
import { BluesoundMapper } from './bluesound.mapper.js';
import type { IBluesoundCommandRequest, IBluesoundConfig, IBluesoundPairedPlayer } from './bluesound.types.js';

export class BluesoundIntegration extends BaseIntegration<IBluesoundConfig> {
  public readonly domain = 'bluesound';
  public readonly displayName = 'Bluesound';
  public readonly status = 'control-runtime' as const;
  public readonly discoveryDescriptor = createBluesoundDiscoveryDescriptor();
  public readonly configFlow = new BluesoundConfigFlow();
  public readonly metadata = {
    source: 'home-assistant/core',
    upstreamPath: 'homeassistant/components/bluesound',
    upstreamDomain: 'bluesound',
    integrationType: 'device',
    iotClass: 'local_polling',
    requirements: ['pyblu==2.0.6'],
    dependencies: [],
    afterDependencies: ['zeroconf'],
    codeowners: ['@thrawnarn', '@LouisChrist'],
    configFlow: true,
    documentation: 'https://www.home-assistant.io/integrations/bluesound',
    zeroconf: ['_musc._tcp.local.'],
  };

  public async setup(configArg: IBluesoundConfig, contextArg: IIntegrationSetupContext): Promise<IIntegrationRuntime> {
    void contextArg;
    return new BluesoundRuntime(new BluesoundClient(configArg));
  }

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

export class HomeAssistantBluesoundIntegration extends BluesoundIntegration {}

class BluesoundRuntime implements IIntegrationRuntime {
  public domain = 'bluesound';

  constructor(private readonly client: BluesoundClient) {}

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

  public async entities(): Promise<IIntegrationEntity[]> {
    return BluesoundMapper.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 === 'bluesound') {
        return await this.callBluesoundService(requestArg);
      }
      return { success: false, error: `Unsupported Bluesound 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 === 'media_play' || requestArg.service === 'play') {
      return this.execute({ command: 'play', playerId: await this.playerIdFromRequest(requestArg) });
    }
    if (requestArg.service === 'media_pause' || requestArg.service === 'pause') {
      return this.execute({ command: 'pause', playerId: await this.playerIdFromRequest(requestArg) });
    }
    if (requestArg.service === 'media_stop' || requestArg.service === 'stop') {
      return this.execute({ command: 'stop', playerId: await this.playerIdFromRequest(requestArg) });
    }
    if (requestArg.service === 'media_next_track' || requestArg.service === 'next_track' || requestArg.service === 'next') {
      return this.execute({ command: 'next_track', playerId: await this.playerIdFromRequest(requestArg) });
    }
    if (requestArg.service === 'media_previous_track' || requestArg.service === 'previous_track' || requestArg.service === 'previous') {
      return this.execute({ command: 'previous_track', playerId: await this.playerIdFromRequest(requestArg) });
    }
    if (requestArg.service === 'media_seek' || requestArg.service === 'seek') {
      return this.execute({ command: 'seek', playerId: await this.playerIdFromRequest(requestArg), position: this.numberData(requestArg, 'seek_position') ?? this.numberData(requestArg, 'position') });
    }
    if (requestArg.service === 'volume_set' || requestArg.service === 'set_volume') {
      return this.execute({ command: 'set_volume', playerId: await this.playerIdFromRequest(requestArg), volumeLevel: this.numberData(requestArg, 'volume_level'), volume: this.numberData(requestArg, 'volume') });
    }
    if (requestArg.service === 'volume_mute' || requestArg.service === 'mute') {
      return this.execute({ command: 'mute', playerId: await this.playerIdFromRequest(requestArg), muted: this.booleanData(requestArg, 'is_volume_muted') ?? this.booleanData(requestArg, 'muted') ?? this.booleanData(requestArg, 'mute') });
    }
    if (requestArg.service === 'select_source' || requestArg.service === 'source') {
      return this.execute({ command: 'select_source', playerId: await this.playerIdFromRequest(requestArg), source: this.stringData(requestArg, 'source') });
    }
    if (requestArg.service === 'play_media') {
      return this.execute({ command: 'play_media', playerId: await this.playerIdFromRequest(requestArg), mediaId: this.stringData(requestArg, 'media_content_id') || this.stringData(requestArg, 'mediaId') || this.stringData(requestArg, 'uri'), mediaType: this.stringData(requestArg, 'media_content_type') });
    }
    if (requestArg.service === 'clear_playlist') {
      return this.execute({ command: 'clear_playlist', playerId: await this.playerIdFromRequest(requestArg) });
    }
    if (requestArg.service === 'shuffle_set') {
      return this.execute({ command: 'shuffle', playerId: await this.playerIdFromRequest(requestArg), shuffle: this.booleanData(requestArg, 'shuffle') });
    }
    if (requestArg.service === 'join' || requestArg.service === 'join_players') {
      return this.execute(await this.joinCommandRequest(requestArg));
    }
    if (requestArg.service === 'unjoin' || requestArg.service === 'unjoin_player') {
      return this.execute({ command: 'unjoin', playerId: await this.playerIdFromRequest(requestArg) });
    }
    return { success: false, error: `Unsupported Bluesound media_player service: ${requestArg.service}` };
  }

  private async callBluesoundService(requestArg: IServiceCallRequest): Promise<IServiceCallResult> {
    if (requestArg.service === 'join' || requestArg.service === 'join_players' || requestArg.service === 'unjoin' || requestArg.service === 'unjoin_player') {
      return this.callMediaPlayerService({ ...requestArg, domain: 'media_player' });
    }
    if (requestArg.service === 'play_preset' || requestArg.service === 'preset') {
      return this.execute({
        command: 'play_preset',
        playerId: await this.playerIdFromRequest(requestArg),
        presetId: this.numberData(requestArg, 'preset_id') ?? this.numberData(requestArg, 'presetId') ?? this.numberData(requestArg, 'id'),
        presetName: this.stringData(requestArg, 'preset') || this.stringData(requestArg, 'preset_name') || this.stringData(requestArg, 'name'),
      });
    }
    if (requestArg.service === 'play' || requestArg.service === 'pause' || requestArg.service === 'stop' || requestArg.service === 'media_play' || requestArg.service === 'media_pause' || requestArg.service === 'media_stop' || requestArg.service === 'volume_set' || requestArg.service === 'set_volume' || requestArg.service === 'volume_mute' || requestArg.service === 'mute' || requestArg.service === 'select_source' || requestArg.service === 'source' || requestArg.service === 'clear_playlist' || requestArg.service === 'shuffle_set') {
      return this.callMediaPlayerService({ ...requestArg, domain: 'media_player' });
    }
    return { success: false, error: `Unsupported Bluesound service: ${requestArg.service}` };
  }

  private async execute(requestArg: IBluesoundCommandRequest): Promise<IServiceCallResult> {
    return { success: true, data: await this.client.execute(requestArg) };
  }

  private async joinCommandRequest(requestArg: IServiceCallRequest): Promise<IBluesoundCommandRequest> {
    const leaderId = await this.playerIdFromRequest(requestArg);
    const playerIds = await this.joinPlayerIdsFromRequest(requestArg);
    const followers = this.followersFromRequest(requestArg);
    return {
      command: 'join',
      playerId: leaderId,
      playerIds: [leaderId, ...playerIds.filter((playerIdArg) => playerIdArg !== leaderId)],
      followers,
    };
  }

  private async playerIdFromRequest(requestArg: IServiceCallRequest): Promise<string> {
    const direct = this.stringData(requestArg, 'player_id') || this.stringData(requestArg, 'playerId') || this.stringData(requestArg, 'id');
    if (direct) {
      return direct;
    }
    const snapshot = await this.client.getSnapshot();
    if (requestArg.target.entityId) {
      const entityPlayerId = BluesoundMapper.entityPlayerId(snapshot, requestArg.target.entityId);
      if (entityPlayerId) {
        return entityPlayerId;
      }
    }
    if (requestArg.target.deviceId) {
      const player = snapshot.players.find((playerArg) => BluesoundMapper.playerDeviceId(playerArg) === requestArg.target.deviceId);
      if (player) {
        return player.syncStatus.id;
      }
    }
    if (snapshot.players.length === 1) {
      return snapshot.players[0].syncStatus.id;
    }
    throw new Error('Bluesound service call requires data.player_id or a target Bluesound media_player entity.');
  }

  private async joinPlayerIdsFromRequest(requestArg: IServiceCallRequest): Promise<string[]> {
    const direct = this.stringArrayData(requestArg, 'player_ids') || this.stringArrayData(requestArg, 'playerIds');
    if (direct?.length) {
      return direct;
    }
    const members = this.stringArrayData(requestArg, 'group_members') || this.stringArrayData(requestArg, 'groupMembers');
    if (!members?.length) {
      return [];
    }
    const snapshot = await this.client.getSnapshot();
    return members.map((memberArg) => BluesoundMapper.entityPlayerId(snapshot, memberArg) || memberArg);
  }

  private followersFromRequest(requestArg: IServiceCallRequest): IBluesoundPairedPlayer[] | undefined {
    const host = this.stringData(requestArg, 'host') || this.stringData(requestArg, 'follower_host') || this.stringData(requestArg, 'followerHost');
    if (!host) {
      return undefined;
    }
    return [{ ip: host, port: this.numberData(requestArg, 'port') || this.numberData(requestArg, 'follower_port') || 11000 }];
  }

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

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

  private booleanData(requestArg: IServiceCallRequest, keyArg: string): boolean | undefined {
    const value = requestArg.data?.[keyArg];
    if (typeof value === 'boolean') {
      return value;
    }
    if (typeof value === 'string') {
      if (value.toLowerCase() === 'true') {
        return true;
      }
      if (value.toLowerCase() === 'false') {
        return false;
      }
    }
    return 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;
  }
}
