import type * as shxInterfaces from '@smarthome.exchange/interfaces';
import { BaseIntegration } from '../../core/classes.baseintegration.js';
import type { IIntegrationEntity, IIntegrationEvent, IIntegrationRuntime, IIntegrationSetupContext, IServiceCallRequest, IServiceCallResult } from '../../core/types.js';
import { EmbyClient, EmbyUnsupportedLivePushError } from './emby.classes.client.js';
import { EmbyConfigFlow } from './emby.classes.configflow.js';
import { createEmbyDiscoveryDescriptor } from './emby.discovery.js';
import { EmbyMapper } from './emby.mapper.js';
import type { IEmbyConfig, IEmbySession, IEmbySnapshot, TEmbyPlayCommand } from './emby.types.js';
import { embyDefaultHttpPort, embyDefaultHttpsPort, embyDisplayName, embyDomain } from './emby.types.js';

export class EmbyIntegration extends BaseIntegration<IEmbyConfig> {
  public readonly domain = embyDomain;
  public readonly displayName = embyDisplayName;
  public readonly status = 'control-runtime' as const;
  public readonly discoveryDescriptor = createEmbyDiscoveryDescriptor();
  public readonly configFlow = new EmbyConfigFlow();
  public readonly metadata = {
    source: 'home-assistant/core',
    upstreamPath: 'homeassistant/components/emby',
    upstreamDomain: embyDomain,
    integrationType: 'service',
    iotClass: 'local_push',
    qualityScale: 'legacy',
    requirements: ['pyEmby==1.10'],
    dependencies: [] as string[],
    afterDependencies: [] as string[],
    codeowners: ['@mezz64'],
    configFlow: true,
    documentation: 'https://www.home-assistant.io/integrations/emby',
    discovery: {
      manual: true,
      mdns: 'Best-effort Emby HTTP/mDNS advertisements when present',
      ssdp: 'Emby DLNA/UPnP advertisements containing Emby metadata',
    },
    runtime: {
      type: 'control-runtime',
      polling: `local Emby REST snapshots via http:${embyDefaultHttpPort}/https:${embyDefaultHttpsPort}, injected clients, or static snapshots`,
      services: ['snapshot', 'refresh', 'media_player controls', 'remote.send_command', 'emby.play_media_shuffle'],
      controls: true,
      livePush: false,
    },
    localApi: {
      implemented: [
        'GET /System/Info/Public for server metadata without a token',
        'GET /System/Info and GET /Sessions with an Emby API key/token',
        'POST /Sessions/{Id}/Playing/{Command} for play, pause, stop, next, previous, and seek',
        'POST /Sessions/{Id}/Command for general remote commands and volume',
        'POST /Sessions/{Id}/Playing for play_media commands',
        'static snapshot/manual sessions and injected client/executor operation',
      ],
      explicitUnsupported: [
        'pyemby websocket/live push event stream in this native runtime',
        'cloud/remote Emby Connect discovery or authentication',
        'pretending media/session commands succeeded without local API token, injected client, or commandExecutor',
      ],
    },
  };

  public async setup(configArg: IEmbyConfig, contextArg: IIntegrationSetupContext): Promise<IIntegrationRuntime> {
    void contextArg;
    return new EmbyRuntime(new EmbyClient(configArg));
  }

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

export class HomeAssistantEmbyIntegration extends EmbyIntegration {}

class EmbyRuntime implements IIntegrationRuntime {
  public domain = embyDomain;

  constructor(private readonly client: EmbyClient) {}

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

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

  public async subscribe(handlerArg: (eventArg: IIntegrationEvent) => void): Promise<() => Promise<void>> {
    void handlerArg;
    throw new EmbyUnsupportedLivePushError();
  }

  public async callService(requestArg: IServiceCallRequest): Promise<IServiceCallResult> {
    try {
      if (requestArg.domain === 'media_player') {
        return await this.callMediaPlayerService(requestArg);
      }
      if (requestArg.domain === 'remote') {
        return await this.callRemoteService(requestArg);
      }
      if (requestArg.domain === embyDomain) {
        return await this.callEmbyService(requestArg);
      }
      return { success: false, error: `Unsupported Emby 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> {
    const session = await this.targetSession(requestArg);
    if (!session) {
      return { success: false, error: 'Emby media_player service requires a target session when multiple sessions are active.' };
    }
    if (requestArg.service === 'play' || requestArg.service === 'media_play') {
      await this.client.play(session.Id);
      return { success: true };
    }
    if (requestArg.service === 'pause' || requestArg.service === 'media_pause') {
      await this.client.pause(session.Id);
      return { success: true };
    }
    if (requestArg.service === 'play_pause' || requestArg.service === 'media_play_pause') {
      await this.client.playPause(session.Id);
      return { success: true };
    }
    if (requestArg.service === 'stop' || requestArg.service === 'media_stop') {
      await this.client.stop(session.Id);
      return { success: true };
    }
    if (requestArg.service === 'next' || requestArg.service === 'next_track' || requestArg.service === 'media_next_track') {
      await this.client.nextTrack(session.Id);
      return { success: true };
    }
    if (requestArg.service === 'previous' || requestArg.service === 'previous_track' || requestArg.service === 'media_previous_track') {
      await this.client.previousTrack(session.Id);
      return { success: true };
    }
    if (requestArg.service === 'seek' || requestArg.service === 'media_seek') {
      const position = requestArg.data?.seek_position ?? requestArg.data?.position;
      if (typeof position !== 'number') {
        return { success: false, error: 'Emby seek requires data.seek_position.' };
      }
      await this.client.seek(session.Id, position);
      return { success: true };
    }
    if (requestArg.service === 'volume' || requestArg.service === 'volume_set') {
      const level = requestArg.data?.volume_level ?? requestArg.data?.level ?? requestArg.data?.volume;
      if (typeof level !== 'number') {
        return { success: false, error: 'Emby volume_set requires data.volume_level.' };
      }
      await this.client.setVolumeLevel(session.Id, level);
      return { success: true };
    }
    if (requestArg.service === 'volume_up') {
      await this.client.volumeUp(session.Id);
      return { success: true };
    }
    if (requestArg.service === 'volume_down') {
      await this.client.volumeDown(session.Id);
      return { success: true };
    }
    if (requestArg.service === 'volume_mute') {
      const muted = requestArg.data?.is_volume_muted ?? requestArg.data?.muted;
      if (typeof muted !== 'boolean') {
        return { success: false, error: 'Emby volume_mute requires data.is_volume_muted.' };
      }
      if (muted) {
        await this.client.mute(session.Id);
      } else {
        await this.client.unmute(session.Id);
      }
      return { success: true };
    }
    if (requestArg.service === 'play_media') {
      return await this.playMedia(session, requestArg, this.playCommandFromRequest(requestArg));
    }
    return { success: false, error: `Unsupported Emby media_player service: ${requestArg.service}` };
  }

  private async callRemoteService(requestArg: IServiceCallRequest): Promise<IServiceCallResult> {
    if (requestArg.service !== 'send_command') {
      return { success: false, error: `Unsupported Emby remote service: ${requestArg.service}` };
    }
    const session = await this.targetSession(requestArg);
    if (!session) {
      return { success: false, error: 'Emby remote.send_command requires a target session when multiple sessions are active.' };
    }
    if (session.SupportsRemoteControl === false) {
      return { success: false, error: 'Target Emby session does not support remote control.' };
    }
    const commands = this.commandsFromRequest(requestArg);
    if (!commands.length) {
      return { success: false, error: 'Emby remote.send_command requires data.command.' };
    }
    const repeats = this.numberValue(requestArg.data?.num_repeats) || 1;
    const delayMs = (this.numberValue(requestArg.data?.delay_secs) || 0) * 1000;
    for (let index = 0; index < repeats; index++) {
      for (const command of commands) {
        await this.client.sendGeneralCommand(session.Id, command);
        if (delayMs > 0) {
          await new Promise((resolveArg) => setTimeout(resolveArg, delayMs));
        }
      }
    }
    return { success: true };
  }

  private async callEmbyService(requestArg: IServiceCallRequest): Promise<IServiceCallResult> {
    if (requestArg.service === 'snapshot' || requestArg.service === 'status') {
      return { success: true, data: await this.client.getSnapshot() };
    }
    if (requestArg.service === 'refresh') {
      const result = await this.client.refresh();
      return { success: result.success, error: result.error, data: result.snapshot || result.data };
    }
    const session = await this.targetSession(requestArg);
    if (!session) {
      return { success: false, error: 'Emby service requires a target session when multiple sessions are active.' };
    }
    if (requestArg.service === 'play_media_shuffle') {
      return await this.playMedia(session, requestArg, 'PlayShuffle');
    }
    if (requestArg.service === 'send_command') {
      return await this.callRemoteService({ ...requestArg, domain: 'remote', service: 'send_command' });
    }
    return { success: false, error: `Unsupported Emby service: ${requestArg.service}` };
  }

  private async playMedia(sessionArg: IEmbySession, requestArg: IServiceCallRequest, commandArg: TEmbyPlayCommand): Promise<IServiceCallResult> {
    const mediaId = requestArg.data?.media_content_id ?? requestArg.data?.mediaId ?? requestArg.data?.itemId;
    if (typeof mediaId !== 'string' || !mediaId) {
      return { success: false, error: 'Emby play_media requires data.media_content_id.' };
    }
    await this.client.playMedia(sessionArg.Id, [mediaId], commandArg);
    return { success: true };
  }

  private async targetSession(requestArg: IServiceCallRequest): Promise<IEmbySession | undefined> {
    const snapshot = await this.client.getSnapshot();
    const sessions = EmbyMapper.activeSessions(snapshot);
    const requestedSessionId = typeof requestArg.data?.sessionId === 'string' ? requestArg.data.sessionId : undefined;
    if (requestedSessionId) {
      return sessions.find((sessionArg) => sessionArg.Id === requestedSessionId);
    }
    const entityId = requestArg.target.entityId;
    const deviceId = requestArg.target.deviceId;
    if (entityId || deviceId) {
      return this.findSessionByTarget(snapshot, sessions, entityId, deviceId);
    }
    return sessions.length === 1 ? sessions[0] : undefined;
  }

  private findSessionByTarget(snapshotArg: IEmbySnapshot, sessionsArg: IEmbySession[], entityIdArg: string | undefined, deviceIdArg: string | undefined): IEmbySession | undefined {
    const entities = EmbyMapper.toEntities(snapshotArg);
    for (const session of sessionsArg) {
      const sessionDeviceId = EmbyMapper.sessionDeviceId(session);
      const entity = entities.find((entityArg) => entityArg.deviceId === sessionDeviceId && entityArg.platform === 'media_player');
      if (deviceIdArg && deviceIdArg === sessionDeviceId) {
        return session;
      }
      if (entityIdArg && entityIdArg === entity?.id) {
        return session;
      }
    }
    return undefined;
  }

  private commandsFromRequest(requestArg: IServiceCallRequest): string[] {
    const command = requestArg.data?.command ?? requestArg.data?.commands;
    if (typeof command === 'string') {
      return [command];
    }
    if (Array.isArray(command)) {
      return command.filter((itemArg): itemArg is string => typeof itemArg === 'string' && Boolean(itemArg));
    }
    return [];
  }

  private playCommandFromRequest(requestArg: IServiceCallRequest): TEmbyPlayCommand {
    const playCommand = requestArg.data?.play_command ?? requestArg.data?.playCommand;
    if (playCommand === 'PlayNow' || playCommand === 'PlayNext' || playCommand === 'PlayLast' || playCommand === 'PlayInstantMix' || playCommand === 'PlayShuffle') {
      return playCommand;
    }
    const enqueue = requestArg.data?.enqueue ?? requestArg.data?.media_enqueue;
    if (enqueue === 'next') {
      return 'PlayNext';
    }
    if (enqueue === 'add') {
      return 'PlayLast';
    }
    return 'PlayNow';
  }

  private numberValue(valueArg: unknown): number | undefined {
    return typeof valueArg === 'number' && Number.isFinite(valueArg) ? valueArg : undefined;
  }
}
