import { BaseIntegration } from '../../core/classes.baseintegration.js';
import type { IIntegrationEntity, IIntegrationEvent, IIntegrationRuntime, IIntegrationSetupContext, IServiceCallRequest, IServiceCallResult } from '../../core/types.js';
import type * as shxInterfaces from '@smarthome.exchange/interfaces';
import { ForkedDaapdClient } from './forked_daapd.classes.client.js';
import { ForkedDaapdConfigFlow } from './forked_daapd.classes.configflow.js';
import { createForkedDaapdDiscoveryDescriptor } from './forked_daapd.discovery.js';
import { ForkedDaapdMapper } from './forked_daapd.mapper.js';
import type { IForkedDaapdCommandRequest, IForkedDaapdConfig } from './forked_daapd.types.js';

export class ForkedDaapdIntegration extends BaseIntegration<IForkedDaapdConfig> {
  public readonly domain = 'forked_daapd';
  public readonly displayName = 'OwnTone';
  public readonly status = 'control-runtime' as const;
  public readonly discoveryDescriptor = createForkedDaapdDiscoveryDescriptor();
  public readonly configFlow = new ForkedDaapdConfigFlow();
  public readonly metadata = {
    source: 'home-assistant/core',
    upstreamPath: 'homeassistant/components/forked_daapd',
    upstreamDomain: 'forked_daapd',
    iotClass: 'local_push',
    requirements: ['pyforked-daapd==0.1.14', 'pylibrespot-java==0.1.1'],
    dependencies: [],
    afterDependencies: ['spotify'],
    codeowners: ['@uvjustin'],
    configFlow: true,
    documentation: 'https://www.home-assistant.io/integrations/forked_daapd',
    zeroconf: ['_daap._tcp.local.'],
  };

  public async setup(configArg: IForkedDaapdConfig, contextArg: IIntegrationSetupContext): Promise<IIntegrationRuntime> {
    return new ForkedDaapdRuntime(new ForkedDaapdClient(configArg), contextArg);
  }

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

export class HomeAssistantForkedDaapdIntegration extends ForkedDaapdIntegration {}

class ForkedDaapdRuntime implements IIntegrationRuntime {
  public domain = 'forked_daapd';
  private unsubscribeNotifications?: () => Promise<void>;

  constructor(private readonly client: ForkedDaapdClient, private readonly context: IIntegrationSetupContext) {}

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

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

  public async subscribe(handlerArg: (eventArg: IIntegrationEvent) => void): Promise<() => Promise<void>> {
    this.unsubscribeNotifications = await this.client.subscribeToNotifications(async (snapshotArg, eventTypesArg) => {
      handlerArg({
        type: 'state_changed',
        integrationDomain: this.domain,
        deviceId: ForkedDaapdMapper.serverDeviceId(snapshotArg),
        entityId: ForkedDaapdMapper.serverEntityId(snapshotArg),
        data: { eventTypes: eventTypesArg, snapshot: snapshotArg },
        timestamp: Date.now(),
      });
    });
    return async () => {
      await this.unsubscribeNotifications?.();
      this.unsubscribeNotifications = undefined;
    };
  }

  public async callService(requestArg: IServiceCallRequest): Promise<IServiceCallResult> {
    try {
      if (requestArg.domain === 'media_player') {
        return await this.callMediaPlayerService(requestArg);
      }
      if (requestArg.domain === 'forked_daapd') {
        return await this.callOwnToneService(requestArg);
      }
      return { success: false, error: `Unsupported OwnTone service domain: ${requestArg.domain}` };
    } catch (errorArg) {
      return { success: false, error: errorArg instanceof Error ? errorArg.message : String(errorArg) };
    }
  }

  public async destroy(): Promise<void> {
    await this.unsubscribeNotifications?.();
    await this.client.destroy();
  }

  private async callMediaPlayerService(requestArg: IServiceCallRequest): Promise<IServiceCallResult> {
    const snapshot = await this.client.getSnapshot();
    const target = this.targetFromRequest(snapshot, requestArg);
    if (target.kind === 'output') {
      return { success: true, data: await this.client.execute(await this.outputCommandRequest(target.outputId, requestArg, snapshot)) };
    }
    const command = this.commandFromMediaService(requestArg.service);
    if (!command) {
      return { success: false, error: `Unsupported OwnTone media_player service: ${requestArg.service}` };
    }
    return { success: true, data: await this.client.execute(await this.serverCommandRequest(command, requestArg, snapshot)) };
  }

  private async callOwnToneService(requestArg: IServiceCallRequest): Promise<IServiceCallResult> {
    if (requestArg.service === 'raw_request' || requestArg.service === 'request') {
      return {
        success: true,
        data: await this.client.execute({
          command: 'raw_request',
          method: this.methodData(requestArg) || 'GET',
          endpoint: this.stringData(requestArg, 'endpoint') || this.stringData(requestArg, 'path'),
          params: this.recordData(requestArg, 'params') as Record<string, string | number | boolean | undefined> | undefined,
          body: requestArg.data?.body,
          expectJson: this.booleanData(requestArg, 'expect_json') ?? this.booleanData(requestArg, 'expectJson') ?? true,
        }),
      };
    }
    if (requestArg.service === 'update' || requestArg.service === 'rescan') {
      return { success: true, data: await this.client.execute({ command: 'raw_request', method: 'PUT', endpoint: requestArg.service }) };
    }
    if (requestArg.service === 'clear_queue') {
      return { success: true, data: await this.client.execute({ command: 'clear_queue' }) };
    }
    const command = this.commandFromMediaService(requestArg.service);
    if (command) {
      return this.callMediaPlayerService({ ...requestArg, domain: 'media_player' });
    }
    return { success: false, error: `Unsupported OwnTone service: ${requestArg.service}` };
  }

  private async outputCommandRequest(outputIdArg: string, requestArg: IServiceCallRequest, snapshotArg: Awaited<ReturnType<ForkedDaapdClient['getSnapshot']>>): Promise<IForkedDaapdCommandRequest> {
    if (requestArg.service === 'turn_on') {
      return { command: 'set_output', outputId: outputIdArg, selected: true };
    }
    if (requestArg.service === 'turn_off') {
      return { command: 'set_output', outputId: outputIdArg, selected: false };
    }
    if (requestArg.service === 'toggle') {
      const output = snapshotArg.outputs.find((outputArg) => outputArg.id === outputIdArg);
      return { command: 'set_output', outputId: outputIdArg, selected: !output?.selected };
    }
    if (requestArg.service === 'volume_set' || requestArg.service === 'set_volume') {
      return { command: 'set_volume', outputId: outputIdArg, volumeLevel: this.numberData(requestArg, 'volume_level'), volume: this.numberData(requestArg, 'volume') };
    }
    if (requestArg.service === 'volume_mute' || requestArg.service === 'mute') {
      return { command: 'mute', outputId: outputIdArg, muted: this.booleanData(requestArg, 'is_volume_muted') ?? this.booleanData(requestArg, 'muted') ?? this.booleanData(requestArg, 'mute') };
    }
    throw new Error(`OwnTone output ${outputIdArg} does not support media_player service ${requestArg.service}.`);
  }

  private async serverCommandRequest(commandArg: IForkedDaapdCommandRequest['command'], requestArg: IServiceCallRequest, snapshotArg: Awaited<ReturnType<ForkedDaapdClient['getSnapshot']>>): Promise<IForkedDaapdCommandRequest> {
    const base: IForkedDaapdCommandRequest = { command: commandArg };
    if (commandArg === 'set_volume') {
      base.volumeLevel = this.numberData(requestArg, 'volume_level');
      base.volume = this.numberData(requestArg, 'volume');
    }
    if (commandArg === 'mute') {
      base.muted = this.booleanData(requestArg, 'is_volume_muted') ?? this.booleanData(requestArg, 'muted') ?? this.booleanData(requestArg, 'mute');
    }
    if (commandArg === 'seek') {
      base.position = this.numberData(requestArg, 'seek_position') ?? this.numberData(requestArg, 'position');
    }
    if (commandArg === 'shuffle') {
      base.shuffle = this.booleanData(requestArg, 'shuffle') ?? this.booleanData(requestArg, 'shuffle_set');
    }
    if (commandArg === 'repeat') {
      const repeat = this.stringData(requestArg, 'repeat') || this.stringData(requestArg, 'repeat_mode');
      if (repeat) {
        base.repeat = repeat;
      }
    }
    if (commandArg === 'play_media') {
      base.mediaId = this.stringData(requestArg, 'media_content_id') || this.stringData(requestArg, 'mediaId') || this.stringData(requestArg, 'uri');
      base.mediaType = this.stringData(requestArg, 'media_content_type') || this.stringData(requestArg, 'mediaType');
      base.clear = this.booleanData(requestArg, 'clear');
    }
    if (commandArg === 'select_source') {
      base.source = this.stringData(requestArg, 'source') || this.stringData(requestArg, 'option');
    }
    if (commandArg === 'turn_on') {
      base.outputIds = snapshotArg.outputs.map((outputArg) => outputArg.id);
      base.volumeLevel = this.numberData(requestArg, 'volume_level');
      base.volume = this.numberData(requestArg, 'volume');
    }
    return base;
  }

  private targetFromRequest(snapshotArg: Awaited<ReturnType<ForkedDaapdClient['getSnapshot']>>, requestArg: IServiceCallRequest): { kind: 'server' } | { kind: 'output'; outputId: string } {
    const outputId = this.stringData(requestArg, 'output_id') || this.stringData(requestArg, 'outputId');
    if (outputId) {
      return { kind: 'output', outputId };
    }
    if (requestArg.target.entityId) {
      const target = ForkedDaapdMapper.entityTarget(snapshotArg, requestArg.target.entityId);
      if (target) {
        return target;
      }
    }
    if (requestArg.target.deviceId) {
      const target = ForkedDaapdMapper.deviceTarget(snapshotArg, requestArg.target.deviceId);
      if (target) {
        return target;
      }
    }
    return { kind: 'server' };
  }

  private commandFromMediaService(serviceArg: string): IForkedDaapdCommandRequest['command'] | undefined {
    if (serviceArg === 'media_play' || serviceArg === 'play') {
      return 'play';
    }
    if (serviceArg === 'media_pause' || serviceArg === 'pause') {
      return 'pause';
    }
    if (serviceArg === 'media_stop' || serviceArg === 'stop') {
      return 'stop';
    }
    if (serviceArg === 'media_next_track' || serviceArg === 'next_track' || serviceArg === 'next') {
      return 'next_track';
    }
    if (serviceArg === 'media_previous_track' || serviceArg === 'previous_track' || serviceArg === 'previous') {
      return 'previous_track';
    }
    if (serviceArg === 'media_seek' || serviceArg === 'seek') {
      return 'seek';
    }
    if (serviceArg === 'volume_set' || serviceArg === 'set_volume') {
      return 'set_volume';
    }
    if (serviceArg === 'volume_mute' || serviceArg === 'mute') {
      return 'mute';
    }
    if (serviceArg === 'clear_playlist' || serviceArg === 'clear_queue') {
      return 'clear_queue';
    }
    if (serviceArg === 'shuffle_set' || serviceArg === 'set_shuffle') {
      return 'shuffle';
    }
    if (serviceArg === 'repeat_set' || serviceArg === 'set_repeat') {
      return 'repeat';
    }
    if (serviceArg === 'play_media') {
      return 'play_media';
    }
    if (serviceArg === 'select_source' || serviceArg === 'source') {
      return 'select_source';
    }
    if (serviceArg === 'turn_on') {
      return 'turn_on';
    }
    if (serviceArg === 'turn_off') {
      return 'turn_off';
    }
    return undefined;
  }

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

  private numberData(requestArg: IServiceCallRequest, keyArg: string): number | undefined {
    const value = requestArg.data?.[keyArg];
    if (typeof value === 'number' && Number.isFinite(value)) {
      return value;
    }
    if (typeof value === 'string' && value.trim()) {
      const parsed = Number(value);
      return Number.isFinite(parsed) ? parsed : undefined;
    }
    return undefined;
  }

  private booleanData(requestArg: IServiceCallRequest, keyArg: string): boolean | undefined {
    const value = requestArg.data?.[keyArg];
    if (typeof value === 'boolean') {
      return value;
    }
    if (value === 'true') {
      return true;
    }
    if (value === 'false') {
      return false;
    }
    return undefined;
  }

  private recordData(requestArg: IServiceCallRequest, keyArg: string): Record<string, unknown> | undefined {
    const value = requestArg.data?.[keyArg];
    return value && typeof value === 'object' && !Array.isArray(value) ? value as Record<string, unknown> : undefined;
  }

  private methodData(requestArg: IServiceCallRequest): 'GET' | 'PUT' | 'POST' | 'DELETE' | undefined {
    const method = this.stringData(requestArg, 'method')?.toUpperCase();
    return method === 'GET' || method === 'PUT' || method === 'POST' || method === 'DELETE' ? method : undefined;
  }
}
