import * as plugins from '../../plugins.js';
import type { IIntegrationEntity } from '../../core/types.js';
import type { IBluesoundPlayerSnapshot, IBluesoundSnapshot, IBluesoundStatus } from './bluesound.types.js';
import { bluesoundDefaultPort } from './bluesound.types.js';

export class BluesoundMapper {
  public static toDevices(snapshotArg: IBluesoundSnapshot): plugins.shxInterfaces.data.IDeviceDefinition[] {
    const updatedAt = snapshotArg.updatedAt || new Date().toISOString();
    return snapshotArg.players.map((playerArg) => {
      const status = playerArg.status;
      return {
        id: this.playerDeviceId(playerArg),
        integrationDomain: 'bluesound',
        name: playerArg.syncStatus.name,
        protocol: 'http',
        manufacturer: playerArg.syncStatus.brand || 'Bluesound',
        model: playerArg.syncStatus.modelName || playerArg.syncStatus.model,
        online: this.playerAvailable(playerArg),
        features: [
          { id: 'playback', capability: 'media', name: 'Playback', readable: true, writable: true },
          { id: 'volume', capability: 'media', name: 'Volume', readable: true, writable: true, unit: '%' },
          { id: 'muted', capability: 'media', name: 'Muted', readable: true, writable: true },
          { id: 'source', capability: 'media', name: 'Source', readable: true, writable: true },
          { id: 'presets', capability: 'media', name: 'Presets', readable: true, writable: true },
          { id: 'group', capability: 'media', name: 'Group', readable: true, writable: true },
          { id: 'current_title', capability: 'media', name: 'Current title', readable: true, writable: false },
          { id: 'media_position', capability: 'media', name: 'Media position', readable: true, writable: true, unit: 's' },
          { id: 'media_duration', capability: 'media', name: 'Media duration', readable: true, writable: false, unit: 's' },
          { id: 'shuffle', capability: 'media', name: 'Shuffle', readable: true, writable: true },
        ],
        state: [
          { featureId: 'playback', value: this.mediaState(playerArg), updatedAt },
          { featureId: 'volume', value: this.volumePercent(playerArg) ?? null, updatedAt },
          { featureId: 'muted', value: this.muted(playerArg) ?? null, updatedAt },
          { featureId: 'source', value: this.currentSource(playerArg) || null, updatedAt },
          { featureId: 'presets', value: playerArg.presets?.length || 0, updatedAt },
          { featureId: 'group', value: playerArg.syncStatus.group || status.groupName || null, updatedAt },
          { featureId: 'current_title', value: this.mediaTitle(playerArg) || null, updatedAt },
          { featureId: 'media_position', value: this.mediaPosition(status) ?? null, updatedAt },
          { featureId: 'media_duration', value: this.seconds(status.totalSeconds) ?? null, updatedAt },
          { featureId: 'shuffle', value: status.shuffle ?? null, updatedAt },
        ],
        metadata: {
          playerId: playerArg.syncStatus.id,
          macAddress: formatMac(playerArg.syncStatus.mac),
          host: playerArg.host,
          port: playerArg.port || bluesoundDefaultPort,
          modelId: playerArg.syncStatus.model,
          groupRole: this.groupRole(playerArg),
          source: snapshotArg.source,
        },
      };
    });
  }

  public static toEntities(snapshotArg: IBluesoundSnapshot): IIntegrationEntity[] {
    const entities: IIntegrationEntity[] = [];
    for (const player of snapshotArg.players) {
      const base = this.playerEntityBase(player);
      const sourceList = this.sourceList(player);
      const presets = player.presets || [];
      const inputs = player.inputs || [];
      entities.push({
        id: this.playerEntityId(player),
        uniqueId: `bluesound_${this.slug(this.formatUniqueId(player))}`,
        integrationDomain: 'bluesound',
        deviceId: this.playerDeviceId(player),
        platform: 'media_player',
        name: player.syncStatus.name,
        state: this.mediaState(player),
        attributes: {
          deviceClass: 'speaker',
          playerId: player.syncStatus.id,
          bluesoundDeviceName: player.syncStatus.name,
          macAddress: formatMac(player.syncStatus.mac),
          host: player.host,
          port: player.port || bluesoundDefaultPort,
          brand: player.syncStatus.brand,
          model: player.syncStatus.model,
          modelName: player.syncStatus.modelName,
          initialized: player.syncStatus.initialized,
          volumeLevel: this.volumeLevel(player),
          volume: this.volumePercent(player),
          volumeDb: this.isGrouped(player) ? player.syncStatus.volumeDb : player.status.volumeDb,
          isVolumeMuted: this.muted(player),
          source: this.currentSource(player),
          sourceList,
          presets,
          inputs,
          mediaContentType: 'music',
          mediaContentId: player.status.streamUrl,
          mediaTitle: this.mediaTitle(player),
          mediaArtist: this.mediaArtist(player),
          mediaAlbumName: this.isGroupedFollower(player) ? undefined : player.status.album,
          mediaDuration: this.isGroupedFollower(player) ? undefined : this.seconds(player.status.totalSeconds),
          mediaPosition: this.isGroupedFollower(player) ? undefined : this.mediaPosition(player.status),
          mediaImageUrl: this.mediaImageUrl(player),
          canSeek: player.status.canSeek,
          shuffle: player.status.shuffle,
          sleep: player.status.sleep,
          indexing: player.status.indexing,
          groupName: player.syncStatus.group || player.status.groupName,
          groupRole: this.groupRole(player),
          groupMembers: this.groupMembers(snapshotArg, player),
          bluesoundGroup: this.bluesoundGroupNames(snapshotArg, player),
          master: this.isLeader(player),
          leader: player.syncStatus.leader,
          followers: player.syncStatus.followers,
          zone: player.syncStatus.zone,
          zoneLeader: player.syncStatus.zoneLeader,
          zoneFollower: player.syncStatus.zoneFollower,
        },
        available: this.playerAvailable(player),
      });

      entities.push({
        id: `sensor.${base}_bluesound_presets`,
        uniqueId: `bluesound_${this.slug(this.formatUniqueId(player))}_presets`,
        integrationDomain: 'bluesound',
        deviceId: this.playerDeviceId(player),
        platform: 'sensor',
        name: `${player.syncStatus.name} Bluesound Presets`,
        state: presets.length,
        attributes: { playerId: player.syncStatus.id, presets },
        available: this.playerAvailable(player),
      });

      entities.push({
        id: `sensor.${base}_bluesound_sources`,
        uniqueId: `bluesound_${this.slug(this.formatUniqueId(player))}_sources`,
        integrationDomain: 'bluesound',
        deviceId: this.playerDeviceId(player),
        platform: 'sensor',
        name: `${player.syncStatus.name} Bluesound Sources`,
        state: sourceList.length,
        attributes: { playerId: player.syncStatus.id, sourceList, inputs, presets },
        available: this.playerAvailable(player),
      });
    }
    return entities;
  }

  public static entityPlayerId(snapshotArg: IBluesoundSnapshot, entityIdArg: string): string | undefined {
    const entity = this.toEntities(snapshotArg).find((entityArg) => entityArg.id === entityIdArg);
    const playerId = entity?.attributes?.playerId;
    return typeof playerId === 'string' ? playerId : undefined;
  }

  public static playerEntityId(playerArg: IBluesoundPlayerSnapshot): string {
    return `media_player.${this.playerEntityBase(playerArg)}`;
  }

  public static playerDeviceId(playerArg: IBluesoundPlayerSnapshot): string {
    return `bluesound.player.${this.slug(this.deviceUniqueBase(playerArg))}`;
  }

  public static slug(valueArg: string | undefined): string {
    return (valueArg || 'bluesound').toLowerCase().replace(/[^a-z0-9]+/g, '_').replace(/^_+|_+$/g, '') || 'bluesound';
  }

  public static sourceList(playerArg: IBluesoundPlayerSnapshot): string[] {
    const sources = [
      ...(playerArg.presets || []).map((presetArg) => presetArg.name),
      ...(playerArg.inputs || []).map((inputArg) => inputArg.text || inputArg.id),
    ].filter((valueArg): valueArg is string => Boolean(valueArg));
    return [...new Set(sources)];
  }

  private static mediaState(playerArg: IBluesoundPlayerSnapshot): string {
    if (!this.playerAvailable(playerArg)) {
      return 'off';
    }
    if (this.isGroupedFollower(playerArg)) {
      return 'idle';
    }
    if (playerArg.status.state === 'pause') {
      return 'paused';
    }
    if (playerArg.status.state === 'play' || playerArg.status.state === 'stream') {
      return 'playing';
    }
    return 'idle';
  }

  private static currentSource(playerArg: IBluesoundPlayerSnapshot): string | undefined {
    if (this.isGroupedFollower(playerArg)) {
      return undefined;
    }
    const status = playerArg.status;
    if (status.inputId) {
      const input = (playerArg.inputs || []).find((inputArg) => inputArg.id === status.inputId || inputArg.url === status.streamUrl);
      if (input) {
        return input.text || input.id;
      }
    }
    const preset = (playerArg.presets || []).find((presetArg) => presetArg.url === status.streamUrl);
    if (preset) {
      return preset.name;
    }
    return status.service;
  }

  private static groupMembers(snapshotArg: IBluesoundSnapshot, playerArg: IBluesoundPlayerSnapshot): string[] | undefined {
    const leader = this.leaderPlayer(snapshotArg, playerArg);
    if (!leader?.syncStatus.followers?.length) {
      return undefined;
    }
    const ids = [leader.syncStatus.id, ...leader.syncStatus.followers.map((followerArg) => pairedPlayerId(followerArg))];
    return ids
      .map((idArg) => snapshotArg.players.find((candidateArg) => candidateArg.syncStatus.id === idArg))
      .filter((candidateArg): candidateArg is IBluesoundPlayerSnapshot => Boolean(candidateArg))
      .map((candidateArg) => this.playerEntityId(candidateArg));
  }

  private static bluesoundGroupNames(snapshotArg: IBluesoundSnapshot, playerArg: IBluesoundPlayerSnapshot): string[] | undefined {
    const members = this.groupMembers(snapshotArg, playerArg);
    if (!members?.length) {
      return undefined;
    }
    return members
      .map((entityIdArg) => snapshotArg.players.find((candidateArg) => this.playerEntityId(candidateArg) === entityIdArg)?.syncStatus.name)
      .filter((valueArg): valueArg is string => Boolean(valueArg));
  }

  private static leaderPlayer(snapshotArg: IBluesoundSnapshot, playerArg: IBluesoundPlayerSnapshot): IBluesoundPlayerSnapshot | undefined {
    if (this.isLeader(playerArg)) {
      return playerArg;
    }
    const leaderId = playerArg.syncStatus.leader ? pairedPlayerId(playerArg.syncStatus.leader) : undefined;
    return leaderId ? snapshotArg.players.find((candidateArg) => candidateArg.syncStatus.id === leaderId) : undefined;
  }

  private static mediaTitle(playerArg: IBluesoundPlayerSnapshot): string | undefined {
    return this.isGroupedFollower(playerArg) ? undefined : playerArg.status.name;
  }

  private static mediaArtist(playerArg: IBluesoundPlayerSnapshot): string | undefined {
    if (this.isGroupedFollower(playerArg)) {
      return playerArg.syncStatus.group || playerArg.status.groupName;
    }
    return playerArg.status.artist;
  }

  private static mediaImageUrl(playerArg: IBluesoundPlayerSnapshot): string | undefined {
    if (this.isGroupedFollower(playerArg)) {
      return undefined;
    }
    const image = playerArg.status.image;
    if (!image) {
      return undefined;
    }
    if (!image.startsWith('/')) {
      return image;
    }
    const host = playerArg.host;
    if (!host) {
      return image;
    }
    return `http://${formatHost(host)}:${playerArg.port || bluesoundDefaultPort}${image}`;
  }

  private static volumeLevel(playerArg: IBluesoundPlayerSnapshot): number | undefined {
    const volume = this.volumePercent(playerArg);
    return typeof volume === 'number' && volume >= 0 ? volume / 100 : undefined;
  }

  private static volumePercent(playerArg: IBluesoundPlayerSnapshot): number | undefined {
    const volume = this.isGrouped(playerArg) ? playerArg.syncStatus.volume : playerArg.status.volume;
    return typeof volume === 'number' && volume >= 0 ? Math.max(0, Math.min(100, Math.round(volume))) : undefined;
  }

  private static muted(playerArg: IBluesoundPlayerSnapshot): boolean | undefined {
    if (this.isGrouped(playerArg)) {
      return playerArg.syncStatus.muteVolume !== undefined;
    }
    return playerArg.status.mute;
  }

  private static mediaPosition(statusArg: IBluesoundStatus): number | undefined {
    return this.seconds(statusArg.seconds);
  }

  private static seconds(valueArg: number | undefined): number | undefined {
    return typeof valueArg === 'number' && Number.isFinite(valueArg) ? Math.floor(valueArg) : undefined;
  }

  private static groupRole(playerArg: IBluesoundPlayerSnapshot): string | undefined {
    if (this.isLeader(playerArg)) {
      return 'leader';
    }
    if (this.isGroupedFollower(playerArg)) {
      return 'member';
    }
    return undefined;
  }

  private static isGrouped(playerArg: IBluesoundPlayerSnapshot): boolean {
    return this.isLeader(playerArg) || this.isGroupedFollower(playerArg);
  }

  private static isLeader(playerArg: IBluesoundPlayerSnapshot): boolean {
    return Boolean(playerArg.syncStatus.followers?.length);
  }

  private static isGroupedFollower(playerArg: IBluesoundPlayerSnapshot): boolean {
    return Boolean(playerArg.syncStatus.leader);
  }

  private static playerAvailable(playerArg: IBluesoundPlayerSnapshot): boolean {
    return playerArg.available !== false;
  }

  private static playerEntityBase(playerArg: IBluesoundPlayerSnapshot): string {
    return this.slug(playerArg.syncStatus.name || playerArg.syncStatus.id);
  }

  private static deviceUniqueBase(playerArg: IBluesoundPlayerSnapshot): string {
    const mac = formatMac(playerArg.syncStatus.mac) || playerArg.syncStatus.id;
    const port = playerArg.port || bluesoundDefaultPort;
    return port === bluesoundDefaultPort ? mac : `${mac}-${port}`;
  }

  private static formatUniqueId(playerArg: IBluesoundPlayerSnapshot): string {
    return `${formatMac(playerArg.syncStatus.mac) || playerArg.syncStatus.id}-${playerArg.port || bluesoundDefaultPort}`;
  }
}

export const formatBluesoundUniqueId = (macArg: string, portArg: number): string => `${formatMac(macArg) || macArg}-${portArg}`;

const pairedPlayerId = (playerArg: { ip: string; port: number }): string => `${playerArg.ip}:${playerArg.port}`;

const formatMac = (valueArg: string | undefined): string => {
  const clean = (valueArg || '').toLowerCase().replace(/[^a-f0-9]/g, '');
  if (clean.length !== 12) {
    return valueArg?.toLowerCase() || '';
  }
  return clean.match(/.{1,2}/g)?.join(':') || clean;
};

const formatHost = (hostArg: string): string => hostArg.includes(':') && !hostArg.startsWith('[') ? `[${hostArg}]` : hostArg;
