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 { DirectvClient } from './directv.classes.client.js';
import { DirectvConfigFlow } from './directv.classes.configflow.js';
import { createDirectvDiscoveryDescriptor } from './directv.discovery.js';
import { DirectvMapper } from './directv.mapper.js';
import type { IDirectvConfig } from './directv.types.js';
import { directvDefaultDevice, directvDomain } from './directv.types.js';

export class DirectvIntegration extends BaseIntegration<IDirectvConfig> {
  public readonly domain = directvDomain;
  public readonly displayName = 'DirecTV';
  public readonly status = 'control-runtime' as const;
  public readonly discoveryDescriptor = createDirectvDiscoveryDescriptor();
  public readonly configFlow = new DirectvConfigFlow();
  public readonly metadata = {
    source: 'home-assistant/core',
    upstreamPath: 'homeassistant/components/directv',
    upstreamDomain: directvDomain,
    integrationType: 'hub',
    iotClass: 'local_polling',
    requirements: ['directv==0.4.0'],
    dependencies: [],
    afterDependencies: [],
    codeowners: [],
    configFlow: true,
    documentation: 'https://www.home-assistant.io/integrations/directv',
    ssdp: [{ deviceType: 'urn:schemas-upnp-org:device:MediaServer:1', manufacturer: 'DIRECTV' }],
    runtime: {
      type: 'control-runtime',
      polling: 'local DirecTV SHEF HTTP API',
      services: ['snapshot', 'refresh', 'remote.send_command', 'media_player.play_media', 'media_player playback controls'],
      controls: true,
    },
    localApi: {
      implemented: [
        'SSDP DirecTV MediaServer discovery',
        'manual host setup',
        'info/getVersion and info/getLocations snapshots',
        'info/mode and tv/getTuned per-location state',
        'remote/processKey commands',
        'tv/tune channel selection',
      ],
      explicitUnsupported: [
        'cloud DirecTV account APIs',
        'reporting live success without a local HTTP host, injected client, snapshot, or command executor',
      ],
    },
  };

  public async setup(configArg: IDirectvConfig, contextArg: IIntegrationSetupContext): Promise<IIntegrationRuntime> {
    void contextArg;
    return new DirectvRuntime(new DirectvClient(configArg));
  }

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

export class HomeAssistantDirectvIntegration extends DirectvIntegration {}

class DirectvRuntime implements IIntegrationRuntime {
  public domain = directvDomain;

  constructor(private readonly client: DirectvClient) {}

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

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

  public async callService(requestArg: IServiceCallRequest): Promise<IServiceCallResult> {
    try {
      if (requestArg.domain === 'remote') {
        return await this.callRemoteService(requestArg);
      }
      if (requestArg.domain === 'media_player') {
        return await this.callMediaPlayerService(requestArg);
      }
      if (requestArg.domain === directvDomain) {
        return await this.callDirectvService(requestArg);
      }
      return { success: false, error: `Unsupported DirecTV 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 callDirectvService(requestArg: IServiceCallRequest): Promise<IServiceCallResult> {
    if (requestArg.service === 'snapshot' || requestArg.service === 'status') {
      return { success: true, data: await this.client.getSnapshot() };
    }
    if (requestArg.service === 'refresh' || requestArg.service === 'reload') {
      const snapshot = await this.client.refresh();
      return snapshot.source === 'runtime' && !snapshot.online
        ? { success: false, error: snapshot.error || 'DirecTV refresh requires a host, snapshot, injected client, or command executor.', data: snapshot }
        : { success: true, data: snapshot };
    }
    if (requestArg.service === 'send_command') {
      return this.callRemoteService({ ...requestArg, domain: 'remote', service: 'send_command' });
    }
    if (requestArg.service === 'tune') {
      const channel = this.stringData(requestArg, 'channel') || this.stringData(requestArg, 'media_content_id');
      if (!channel) {
        return { success: false, error: 'DirecTV tune requires data.channel.' };
      }
      const address = await this.targetAddress(requestArg);
      return { success: true, data: await this.client.tune(channel, address) };
    }
    return { success: false, error: `Unsupported DirecTV service: ${requestArg.service}` };
  }

  private async callRemoteService(requestArg: IServiceCallRequest): Promise<IServiceCallResult> {
    if (requestArg.service === 'turn_on') {
      return { success: true, data: await this.client.sendRemoteKey('poweron', await this.targetAddress(requestArg)) };
    }
    if (requestArg.service === 'turn_off') {
      return { success: true, data: await this.client.sendRemoteKey('poweroff', await this.targetAddress(requestArg)) };
    }
    if (requestArg.service !== 'send_command') {
      return { success: false, error: `Unsupported DirecTV remote service: ${requestArg.service}` };
    }
    const commands = this.stringArrayData(requestArg, 'command');
    if (!commands?.length) {
      return { success: false, error: 'DirecTV remote.send_command requires data.command.' };
    }
    const repeats = this.repeats(requestArg);
    const address = await this.targetAddress(requestArg);
    let result: unknown;
    for (let index = 0; index < repeats; index += 1) {
      for (const command of commands) {
        result = await this.client.sendRemoteKey(command, address);
      }
    }
    return { success: true, data: result };
  }

  private async callMediaPlayerService(requestArg: IServiceCallRequest): Promise<IServiceCallResult> {
    const address = await this.targetAddress(requestArg);
    if (requestArg.service === 'turn_on') {
      if (address !== directvDefaultDevice) {
        return { success: false, error: 'DirecTV media_player.turn_on is only supported for the main receiver.' };
      }
      return { success: true, data: await this.client.sendRemoteKey('poweron', address) };
    }
    if (requestArg.service === 'turn_off') {
      if (address !== directvDefaultDevice) {
        return { success: false, error: 'DirecTV media_player.turn_off is only supported for the main receiver.' };
      }
      return { success: true, data: await this.client.sendRemoteKey('poweroff', address) };
    }
    if (requestArg.service === 'play' || requestArg.service === 'media_play') {
      return { success: true, data: await this.client.sendRemoteKey('play', address) };
    }
    if (requestArg.service === 'pause' || requestArg.service === 'media_pause') {
      return { success: true, data: await this.client.sendRemoteKey('pause', address) };
    }
    if (requestArg.service === 'stop' || requestArg.service === 'media_stop') {
      return { success: true, data: await this.client.sendRemoteKey('stop', address) };
    }
    if (requestArg.service === 'next_track' || requestArg.service === 'media_next_track') {
      return { success: true, data: await this.client.sendRemoteKey('ffwd', address) };
    }
    if (requestArg.service === 'previous_track' || requestArg.service === 'media_previous_track') {
      return { success: true, data: await this.client.sendRemoteKey('rew', address) };
    }
    if (requestArg.service === 'play_media') {
      const mediaType = this.stringData(requestArg, 'media_content_type') || this.stringData(requestArg, 'media_type');
      const mediaId = this.stringData(requestArg, 'media_content_id') || this.stringData(requestArg, 'media_id') || this.stringData(requestArg, 'channel');
      if (mediaType !== 'channel') {
        return { success: false, error: 'DirecTV play_media only supports media_content_type channel.' };
      }
      if (!mediaId) {
        return { success: false, error: 'DirecTV play_media requires data.media_content_id.' };
      }
      return { success: true, data: await this.client.tune(mediaId, address) };
    }
    return { success: false, error: `Unsupported DirecTV media_player service: ${requestArg.service}` };
  }

  private async targetAddress(requestArg: IServiceCallRequest): Promise<string> {
    const explicit = this.stringData(requestArg, 'clientAddr') || this.stringData(requestArg, 'client_addr') || this.stringData(requestArg, 'address') || this.stringData(requestArg, 'client');
    if (explicit) {
      return explicit;
    }
    const targetId = requestArg.target.entityId || requestArg.target.deviceId;
    if (!targetId) {
      return directvDefaultDevice;
    }
    const snapshot = await this.client.getSnapshot();
    for (const location of snapshot.locations) {
      if (targetId === DirectvMapper.locationDeviceId(snapshot, location) || targetId === DirectvMapper.mediaPlayerEntityId(snapshot, location)) {
        return location.address;
      }
    }
    return directvDefaultDevice;
  }

  private repeats(requestArg: IServiceCallRequest): number {
    const value = requestArg.data?.num_repeats ?? requestArg.data?.numRepeats ?? 1;
    return typeof value === 'number' && Number.isFinite(value) ? Math.max(1, Math.floor(value)) : 1;
  }

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

  private stringArrayData(requestArg: IServiceCallRequest, keyArg: string): string[] | undefined {
    const value = requestArg.data?.[keyArg];
    if (typeof value === 'string' && value) {
      return [value];
    }
    return Array.isArray(value) && value.every((itemArg) => typeof itemArg === 'string' && Boolean(itemArg)) ? value : undefined;
  }
}
