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 { JellyfinClient, JellyfinUnsupportedLivePushError } from './jellyfin.classes.client.js';
import { JellyfinConfigFlow } from './jellyfin.classes.configflow.js';
import { createJellyfinDiscoveryDescriptor } from './jellyfin.discovery.js';
import { JellyfinMapper } from './jellyfin.mapper.js';
import type { IJellyfinConfig, IJellyfinSession, IJellyfinSnapshot, TJellyfinPlayCommand } from './jellyfin.types.js';

export class JellyfinIntegration extends BaseIntegration<IJellyfinConfig> {
  public readonly domain = 'jellyfin';
  public readonly displayName = 'Jellyfin';
  public readonly status = 'control-runtime' as const;
  public readonly discoveryDescriptor = createJellyfinDiscoveryDescriptor();
  public readonly configFlow = new JellyfinConfigFlow();
  public readonly metadata = {
    source: 'home-assistant/core',
    upstreamPath: 'homeassistant/components/jellyfin',
    upstreamDomain: 'jellyfin',
    integrationType: 'service',
    iotClass: 'local_polling',
    requirements: ['jellyfin-apiclient-python==1.11.0'],
    dependencies: [],
    afterDependencies: [],
    codeowners: ['@RunC0deRun', '@ctalkington'],
    configFlow: true,
    documentation: 'https://www.home-assistant.io/integrations/jellyfin',
    nativeRuntime: {
      polling: true,
      restSessionsWithToken: true,
      livePush: false,
      unsupportedLivePushReason: 'Jellyfin WebSocket session push is not implemented in this runtime.',
    },
  };

  public async setup(configArg: IJellyfinConfig, contextArg: IIntegrationSetupContext): Promise<IIntegrationRuntime> {
    void contextArg;
    return new JellyfinRuntime(new JellyfinClient(configArg));
  }

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

export class HomeAssistantJellyfinIntegration extends JellyfinIntegration {}

class JellyfinRuntime implements IIntegrationRuntime {
  public domain = 'jellyfin';

  constructor(private readonly client: JellyfinClient) {}

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

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

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

  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 === 'jellyfin') {
        return await this.callJellyfinService(requestArg);
      }
      return { success: false, error: `Unsupported Jellyfin 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: 'Jellyfin 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: 'Jellyfin 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: 'Jellyfin 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: 'Jellyfin 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 Jellyfin media_player service: ${requestArg.service}` };
  }

  private async callRemoteService(requestArg: IServiceCallRequest): Promise<IServiceCallResult> {
    if (requestArg.service !== 'send_command') {
      return { success: false, error: `Unsupported Jellyfin remote service: ${requestArg.service}` };
    }
    const session = await this.targetSession(requestArg);
    if (!session) {
      return { success: false, error: 'Jellyfin remote.send_command requires a target session when multiple sessions are active.' };
    }
    if (session.SupportsRemoteControl === false) {
      return { success: false, error: 'Target Jellyfin session does not support remote control.' };
    }
    const commands = this.commandsFromRequest(requestArg);
    if (!commands.length) {
      return { success: false, error: 'Jellyfin 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 callJellyfinService(requestArg: IServiceCallRequest): Promise<IServiceCallResult> {
    const session = await this.targetSession(requestArg);
    if (!session) {
      return { success: false, error: 'Jellyfin 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 Jellyfin service: ${requestArg.service}` };
  }

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

  private async targetSession(requestArg: IServiceCallRequest): Promise<IJellyfinSession | undefined> {
    const snapshot = await this.client.getSnapshot();
    const sessions = JellyfinMapper.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: IJellyfinSnapshot, sessionsArg: IJellyfinSession[], entityIdArg: string | undefined, deviceIdArg: string | undefined): IJellyfinSession | undefined {
    const entities = JellyfinMapper.toEntities(snapshotArg);
    for (const session of sessionsArg) {
      const sessionDeviceId = JellyfinMapper.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 || entityIdArg === `remote.${JellyfinMapper.slug(session.DeviceName || session.Client || session.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): TJellyfinPlayCommand {
    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;
  }
}
