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 { SnapcastClient } from './snapcast.classes.client.js';
import { SnapcastConfigFlow } from './snapcast.classes.configflow.js';
import { createSnapcastDiscoveryDescriptor } from './snapcast.discovery.js';
import { SnapcastMapper } from './snapcast.mapper.js';
import type { ISnapcastClient, ISnapcastConfig, ISnapcastGroup, ISnapcastServerStatus } from './snapcast.types.js';

export class SnapcastIntegration extends BaseIntegration<ISnapcastConfig> {
  public readonly domain = 'snapcast';
  public readonly displayName = 'Snapcast';
  public readonly status = 'control-runtime' as const;
  public readonly discoveryDescriptor = createSnapcastDiscoveryDescriptor();
  public readonly configFlow = new SnapcastConfigFlow();
  public readonly metadata = {
    source: 'home-assistant/core',
    upstreamPath: 'homeassistant/components/snapcast',
    upstreamDomain: 'snapcast',
    integrationType: 'hub',
    iotClass: 'local_push',
    requirements: ['snapcast==2.3.7'],
    dependencies: [],
    afterDependencies: [],
    codeowners: ['@luar123'],
    configFlow: true,
    documentation: 'https://www.home-assistant.io/integrations/snapcast',
  };

  public async setup(configArg: ISnapcastConfig, contextArg: IIntegrationSetupContext): Promise<IIntegrationRuntime> {
    void contextArg;
    return new SnapcastRuntime(new SnapcastClient(configArg));
  }

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

export class HomeAssistantSnapcastIntegration extends SnapcastIntegration {}

class SnapcastRuntime implements IIntegrationRuntime {
  public domain = 'snapcast';

  constructor(private readonly client: SnapcastClient) {}

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

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

  public async subscribe(): Promise<() => Promise<void>> {
    throw new Error('Snapcast live event subscription is not implemented in this TypeScript port; poll devices()/entities() or use JSON-RPC service calls.');
  }

  public async callService(requestArg: IServiceCallRequest): Promise<IServiceCallResult> {
    try {
      if (requestArg.domain === 'media_player') {
        return await this.callMediaPlayerService(requestArg);
      }
      if (requestArg.domain === 'snapcast') {
        return await this.callSnapcastService(requestArg);
      }
      return { success: false, error: `Unsupported Snapcast 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 === 'volume_set') {
      const volume = this.volumePercent(requestArg.data?.volume_level ?? requestArg.data?.volume);
      const target = await this.resolveTarget(requestArg);
      if (target.kind === 'group') {
        await this.setVolumeForGroup(target.id, volume);
      } else {
        await this.client.setClientVolume(this.requireClientTarget(target), volume);
      }
      return { success: true };
    }

    if (requestArg.service === 'volume_mute') {
      const muted = this.booleanValue(requestArg.data?.is_volume_muted ?? requestArg.data?.muted ?? requestArg.data?.mute, 'Snapcast volume_mute requires data.is_volume_muted or data.muted.');
      const target = await this.resolveTarget(requestArg);
      if (target.kind === 'group') {
        await this.client.setGroupMuted(target.id, muted);
      } else {
        await this.client.setClientMuted(this.requireClientTarget(target), muted);
      }
      return { success: true };
    }

    if (requestArg.service === 'select_source' || requestArg.service === 'set_stream') {
      await this.setStreamFromService(requestArg);
      return { success: true };
    }

    if (requestArg.service === 'join') {
      await this.joinPlayers(requestArg);
      return { success: true };
    }

    if (requestArg.service === 'unjoin') {
      await this.unjoinPlayer(requestArg);
      return { success: true };
    }

    return { success: false, error: `Unsupported Snapcast media_player service: ${requestArg.service}` };
  }

  private async callSnapcastService(requestArg: IServiceCallRequest): Promise<IServiceCallResult> {
    if (requestArg.service === 'snapshot') {
      return { success: true, data: await this.client.snapshot() };
    }
    if (requestArg.service === 'restore') {
      await this.client.restore();
      return { success: true };
    }
    if (requestArg.service === 'set_latency') {
      const latency = this.numberValue(requestArg.data?.latency, 'Snapcast set_latency requires data.latency.');
      const target = await this.resolveTarget(requestArg);
      await this.client.setClientLatency(this.requireClientTarget(target), latency);
      return { success: true };
    }
    if (requestArg.service === 'set_stream') {
      await this.setStreamFromService(requestArg);
      return { success: true };
    }
    if (requestArg.service === 'set_group_mute' || requestArg.service === 'group_mute') {
      const muted = this.booleanValue(requestArg.data?.mute ?? requestArg.data?.muted, 'Snapcast set_group_mute requires data.mute or data.muted.');
      const target = await this.resolveTarget(requestArg);
      await this.client.setGroupMuted(await this.groupIdFromTarget(target), muted);
      return { success: true };
    }
    if (requestArg.service === 'set_clients' || requestArg.service === 'group_set_clients') {
      const target = await this.resolveTarget(requestArg);
      const groupId = await this.groupIdFromTarget(target);
      const clients = await this.clientIdsFromData(requestArg.data?.clients ?? requestArg.data?.client_ids);
      await this.client.setGroupClients(groupId, clients);
      return { success: true };
    }
    if (requestArg.service === 'join') {
      await this.joinPlayers(requestArg);
      return { success: true };
    }
    if (requestArg.service === 'unjoin') {
      await this.unjoinPlayer(requestArg);
      return { success: true };
    }
    if (requestArg.service === 'volume_set' || requestArg.service === 'set_volume') {
      return this.callMediaPlayerService({ ...requestArg, domain: 'media_player', service: 'volume_set' });
    }
    if (requestArg.service === 'volume_mute' || requestArg.service === 'set_mute') {
      return this.callMediaPlayerService({ ...requestArg, domain: 'media_player', service: 'volume_mute' });
    }
    return { success: false, error: `Unsupported Snapcast service: ${requestArg.service}` };
  }

  private async setStreamFromService(requestArg: IServiceCallRequest): Promise<void> {
    const streamId = this.stringValue(requestArg.data?.source ?? requestArg.data?.stream_id ?? requestArg.data?.stream, 'Snapcast set_stream/select_source requires data.source or data.stream_id.');
    const target = await this.resolveTarget(requestArg);
    await this.client.setGroupStream(await this.groupIdFromTarget(target), streamId);
  }

  private async joinPlayers(requestArg: IServiceCallRequest): Promise<void> {
    const target = await this.resolveTarget(requestArg);
    const groupId = await this.groupIdFromTarget(target);
    const status = await this.client.getStatus();
    const group = this.requireGroup(status, groupId);
    const memberIds = await this.clientIdsFromData(requestArg.data?.group_members ?? requestArg.data?.clients ?? requestArg.data?.client_ids);
    await this.client.setGroupClients(groupId, [...new Set([...group.clients.map((clientArg) => clientArg.id), ...memberIds])]);
  }

  private async unjoinPlayer(requestArg: IServiceCallRequest): Promise<void> {
    const target = await this.resolveTarget(requestArg);
    const clientId = this.requireClientTarget(target);
    const status = await this.client.getStatus();
    const group = this.groupForClient(status, clientId);
    if (!group) {
      throw new Error(`Snapcast client has no group: ${clientId}`);
    }
    await this.client.setGroupClients(group.id, group.clients.filter((clientArg) => clientArg.id !== clientId).map((clientArg) => clientArg.id));
  }

  private async setVolumeForGroup(groupIdArg: string, volumeArg: number): Promise<void> {
    const status = await this.client.getStatus();
    const group = this.requireGroup(status, groupIdArg);
    for (const client of group.clients) {
      await this.client.setClientVolume(client.id, volumeArg);
    }
  }

  private async resolveTarget(requestArg: IServiceCallRequest): Promise<{ kind: 'client' | 'group' | 'stream'; id: string }> {
    const status = await this.client.getStatus();
    const directClientId = this.optionalString(requestArg.data?.client_id);
    if (directClientId) {
      return { kind: 'client', id: directClientId };
    }
    const directGroupId = this.optionalString(requestArg.data?.group_id);
    if (directGroupId) {
      return { kind: 'group', id: directGroupId };
    }
    const targetId = requestArg.target.entityId || requestArg.target.deviceId;
    if (!targetId) {
      throw new Error('Snapcast service calls require target.entityId, target.deviceId, data.client_id, or data.group_id.');
    }

    const entity = SnapcastMapper.toEntities(status).find((entityArg) => entityArg.id === targetId || entityArg.deviceId === targetId);
    if (entity?.attributes?.snapcastClientId && typeof entity.attributes.snapcastClientId === 'string') {
      return { kind: 'client', id: entity.attributes.snapcastClientId };
    }
    if (entity?.attributes?.snapcastGroupId && typeof entity.attributes.snapcastGroupId === 'string') {
      return { kind: 'group', id: entity.attributes.snapcastGroupId };
    }
    if (entity?.attributes?.snapcastStreamId && typeof entity.attributes.snapcastStreamId === 'string') {
      return { kind: 'stream', id: entity.attributes.snapcastStreamId };
    }
    throw new Error(`Snapcast target was not found: ${targetId}`);
  }

  private requireClientTarget(targetArg: { kind: 'client' | 'group' | 'stream'; id: string }): string {
    if (targetArg.kind !== 'client') {
      throw new Error(`Snapcast service requires a client target, got ${targetArg.kind}.`);
    }
    return targetArg.id;
  }

  private async groupIdFromTarget(targetArg: { kind: 'client' | 'group' | 'stream'; id: string }): Promise<string> {
    if (targetArg.kind === 'group') {
      return targetArg.id;
    }
    if (targetArg.kind !== 'client') {
      throw new Error(`Snapcast group control requires a client or group target, got ${targetArg.kind}.`);
    }
    const group = this.groupForClient(await this.client.getStatus(), targetArg.id);
    if (!group) {
      throw new Error(`Snapcast client has no group: ${targetArg.id}`);
    }
    return group.id;
  }

  private async clientIdsFromData(valueArg: unknown): Promise<string[]> {
    const values = Array.isArray(valueArg) ? valueArg : typeof valueArg === 'string' ? [valueArg] : [];
    if (!values.length || values.some((itemArg) => typeof itemArg !== 'string')) {
      throw new Error('Snapcast group control requires a clients/client_ids/group_members string array.');
    }
    const status = await this.client.getStatus();
    const entities = SnapcastMapper.toEntities(status);
    return values.map((value) => {
      const entity = entities.find((entityArg) => entityArg.id === value || entityArg.deviceId === value);
      if (entity?.attributes?.snapcastClientId && typeof entity.attributes.snapcastClientId === 'string') {
        return entity.attributes.snapcastClientId;
      }
      return value;
    });
  }

  private groupForClient(statusArg: ISnapcastServerStatus, clientIdArg: string): ISnapcastGroup | undefined {
    return statusArg.groups.find((groupArg) => groupArg.clients.some((clientArg) => clientArg.id === clientIdArg));
  }

  private requireGroup(statusArg: ISnapcastServerStatus, groupIdArg: string): ISnapcastGroup {
    const group = statusArg.groups.find((itemArg) => itemArg.id === groupIdArg);
    if (!group) {
      throw new Error(`Snapcast group not found: ${groupIdArg}`);
    }
    return group;
  }

  private volumePercent(valueArg: unknown): number {
    const value = this.numberValue(valueArg, 'Snapcast volume_set requires data.volume_level or data.volume.');
    return Math.max(0, Math.min(100, Math.round(value <= 1 ? value * 100 : value)));
  }

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

  private optionalString(valueArg: unknown): string | undefined {
    return typeof valueArg === 'string' && valueArg ? valueArg : undefined;
  }
}
