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 { AxisClient } from './axis.classes.client.js';
import { AxisConfigFlow } from './axis.classes.configflow.js';
import { createAxisDiscoveryDescriptor } from './axis.discovery.js';
import { AxisMapper } from './axis.mapper.js';
import type { IAxisConfig } from './axis.types.js';

export class AxisIntegration extends BaseIntegration<IAxisConfig> {
  public readonly domain = 'axis';
  public readonly displayName = 'Axis';
  public readonly status = 'control-runtime' as const;
  public readonly discoveryDescriptor = createAxisDiscoveryDescriptor();
  public readonly configFlow = new AxisConfigFlow();
  public readonly metadata = {
    source: 'home-assistant/core',
    upstreamPath: 'homeassistant/components/axis',
    upstreamDomain: 'axis',
    integrationType: 'device',
    iotClass: 'local_push',
    requirements: ['axis==69'],
    dependencies: [],
    afterDependencies: ['mqtt'],
    codeowners: ['@Kane610'],
    configFlow: true,
    documentation: 'https://www.home-assistant.io/integrations/axis',
    vapix: {
      implemented: [
        'basic-device-info',
        'api-discovery',
        'param-cgi',
        'video-streaming URLs',
        'io-port-management getPorts/setPorts',
        'legacy io/port.cgi output commands',
        'light-control read model',
        'ptz-control command CGI',
      ],
      explicitUnsupported: [
        'RTSP/MJPEG proxying',
        'event stream subscription over RTSP/WebSocket/MQTT',
        'writing snapshots to files',
      ],
    },
  };

  public async setup(configArg: IAxisConfig, contextArg: IIntegrationSetupContext): Promise<IIntegrationRuntime> {
    void contextArg;
    return new AxisRuntime(new AxisClient(configArg));
  }

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

export class HomeAssistantAxisIntegration extends AxisIntegration {}

class AxisRuntime implements IIntegrationRuntime {
  public domain = 'axis';

  constructor(private readonly client: AxisClient) {}

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

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

  public async subscribe(handlerArg: (eventArg: IIntegrationEvent) => void): Promise<() => Promise<void>> {
    void handlerArg;
    throw new Error('Axis live event streaming is not implemented in this TypeScript port; use snapshot/manual event data or poll entities.');
  }

  public async callService(requestArg: IServiceCallRequest): Promise<IServiceCallResult> {
    try {
      const snapshot = await this.client.getSnapshot();
      const relayCommand = AxisMapper.relayCommandForService(snapshot, requestArg);
      if (relayCommand) {
        await this.client.setRelayState(relayCommand.portId, relayCommand.state);
        return { success: true };
      }

      const ptzCommand = AxisMapper.ptzCommandForService(snapshot, requestArg);
      if (ptzCommand) {
        await this.client.ptzControl(ptzCommand);
        return { success: true };
      }

      if ((requestArg.domain === 'camera' && requestArg.service === 'snapshot') || (requestArg.domain === 'axis' && requestArg.service === 'snapshot')) {
        if (typeof requestArg.data?.filename === 'string') {
          return { success: false, error: 'Axis snapshot file writes are not implemented; request data as base64 without data.filename.' };
        }
        const cameraId = this.stringData(requestArg, 'cameraId') || this.stringData(requestArg, 'camera_id') || this.cameraIdFromTarget(snapshot, requestArg);
        const image = await this.client.getCameraSnapshot(cameraId);
        return { success: true, data: { contentType: image.contentType, dataBase64: Buffer.from(image.data).toString('base64') } };
      }

      if ((requestArg.domain === 'camera' && requestArg.service === 'stream_source') || (requestArg.domain === 'axis' && requestArg.service === 'stream_source')) {
        const cameraId = this.stringData(requestArg, 'cameraId') || this.stringData(requestArg, 'camera_id') || this.cameraIdFromTarget(snapshot, requestArg);
        const camera = snapshot.cameras.find((cameraArg) => cameraArg.id === cameraId || String(cameraArg.videoSource) === cameraId) || snapshot.cameras[0];
        if (!camera) {
          return { success: false, error: 'Axis stream_source requires a configured or discovered camera.' };
        }
        return { success: true, data: { streamSource: this.client.streamSource(camera), cameraId: camera.id } };
      }

      if (requestArg.domain === 'axis' && ['subscribe_events', 'start_event_stream', 'enable_events'].includes(requestArg.service)) {
        return { success: false, error: 'Axis live event streaming is not implemented in this TypeScript port.' };
      }

      return { success: false, error: `Unsupported Axis service: ${requestArg.domain}.${requestArg.service}` };
    } catch (errorArg) {
      return { success: false, error: errorArg instanceof Error ? errorArg.message : String(errorArg) };
    }
  }

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

  private cameraIdFromTarget(snapshotArg: Awaited<ReturnType<AxisClient['getSnapshot']>>, requestArg: IServiceCallRequest): string | undefined {
    const entityId = requestArg.target.entityId;
    if (!entityId) {
      return snapshotArg.cameras[0]?.id;
    }
    const entity = AxisMapper.toEntities(snapshotArg).find((entityArg) => entityArg.id === entityId || entityArg.uniqueId === entityId);
    return typeof entity?.attributes?.cameraId === 'string' ? entity.attributes.cameraId : snapshotArg.cameras[0]?.id;
  }

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