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 { DeconzClient } from './deconz.classes.client.js';
import { DeconzConfigFlow } from './deconz.classes.configflow.js';
import { createDeconzDiscoveryDescriptor } from './deconz.discovery.js';
import { DeconzMapper } from './deconz.mapper.js';
import type { IDeconzConfig, IDeconzGroupActionPatch, IDeconzLightStatePatch, IDeconzSensorConfigPatch, IDeconzSensorStatePatch } from './deconz.types.js';

interface IDeconzServiceTarget {
  entity: IIntegrationEntity;
  resource: string;
  resourceId: string;
  stateKey?: string;
  configKey?: string;
}

export class DeconzIntegration extends BaseIntegration<IDeconzConfig> {
  public readonly domain = 'deconz';
  public readonly displayName = 'deCONZ';
  public readonly status = 'control-runtime' as const;
  public readonly discoveryDescriptor = createDeconzDiscoveryDescriptor();
  public readonly configFlow = new DeconzConfigFlow();
  public readonly metadata = {
    source: 'home-assistant/core',
    upstreamPath: 'homeassistant/components/deconz',
    upstreamDomain: 'deconz',
    integrationType: 'hub',
    iotClass: 'local_push',
    requirements: ['pydeconz==120'],
    codeowners: ['@Kane610'],
    documentation: 'https://www.home-assistant.io/integrations/deconz',
    restDocumentation: 'https://dresden-elektronik.github.io/deconz-rest-doc/',
  };

  public async setup(configArg: IDeconzConfig, contextArg: IIntegrationSetupContext): Promise<IIntegrationRuntime> {
    void contextArg;
    return new DeconzRuntime(new DeconzClient(configArg));
  }

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

export class HomeAssistantDeconzIntegration extends DeconzIntegration {}

class DeconzRuntime implements IIntegrationRuntime {
  public domain = 'deconz';

  constructor(private readonly client: DeconzClient) {}

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

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

  public async subscribe(handlerArg: Parameters<NonNullable<IIntegrationRuntime['subscribe']>>[0]): Promise<() => Promise<void>> {
    return this.client.subscribeToEvents((eventArg) => handlerArg(DeconzMapper.toIntegrationEvent(eventArg)));
  }

  public async callService(requestArg: IServiceCallRequest): Promise<IServiceCallResult> {
    const target = await this.resolveTarget(requestArg);
    if (!target) {
      return { success: false, error: 'deCONZ service calls require a known target entityId or deviceId.' };
    }

    if (requestArg.domain === 'light') {
      return this.callLightService(target, requestArg);
    }
    if (requestArg.domain === 'switch') {
      return this.callSwitchService(target, requestArg);
    }
    if (requestArg.domain === 'cover') {
      return this.callCoverService(target, requestArg);
    }
    if (requestArg.domain === 'sensor' || requestArg.domain === 'binary_sensor' || requestArg.domain === 'climate') {
      return this.callSensorService(target, requestArg);
    }

    return { success: false, error: `Unsupported deCONZ service domain: ${requestArg.domain}` };
  }

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

  private async callLightService(targetArg: IDeconzServiceTarget, requestArg: IServiceCallRequest): Promise<IServiceCallResult> {
    if (targetArg.resource !== 'lights' && targetArg.resource !== 'groups') {
      return { success: false, error: 'deCONZ light services require a light or group target.' };
    }

    const payload = this.lightPayload(requestArg);
    if (!payload) {
      return { success: false, error: `Unsupported deCONZ light service: ${requestArg.service}` };
    }

    if (targetArg.resource === 'groups') {
      await this.client.setGroupState(targetArg.resourceId, payload);
    } else {
      await this.client.setLightState(targetArg.resourceId, payload);
    }
    return { success: true };
  }

  private async callSwitchService(targetArg: IDeconzServiceTarget, requestArg: IServiceCallRequest): Promise<IServiceCallResult> {
    if (targetArg.resource !== 'lights') {
      return { success: false, error: 'deCONZ switch services require a light-backed switch target.' };
    }
    if (requestArg.service === 'turn_on') {
      await this.client.setLightState(targetArg.resourceId, { on: true });
      return { success: true };
    }
    if (requestArg.service === 'turn_off') {
      await this.client.setLightState(targetArg.resourceId, { on: false });
      return { success: true };
    }
    return { success: false, error: `Unsupported deCONZ switch service: ${requestArg.service}` };
  }

  private async callCoverService(targetArg: IDeconzServiceTarget, requestArg: IServiceCallRequest): Promise<IServiceCallResult> {
    if (targetArg.resource !== 'lights') {
      return { success: false, error: 'deCONZ cover services require a light-backed cover target.' };
    }

    if (requestArg.service === 'open_cover') {
      await this.client.setLightState(targetArg.resourceId, { open: true });
      return { success: true };
    }
    if (requestArg.service === 'close_cover') {
      await this.client.setLightState(targetArg.resourceId, { open: false });
      return { success: true };
    }
    if (requestArg.service === 'stop_cover') {
      await this.client.setLightState(targetArg.resourceId, { lift: 'stop' });
      return { success: true };
    }
    if (requestArg.service === 'set_position' || requestArg.service === 'set_percentage') {
      const position = this.numberFromData(requestArg.data, requestArg.service === 'set_position' ? ['position'] : ['percentage']);
      if (position === undefined) {
        return { success: false, error: `deCONZ ${requestArg.service} requires a numeric position or percentage.` };
      }
      await this.client.setLightState(targetArg.resourceId, { lift: this.clamp(100 - position, 0, 100) });
      return { success: true };
    }

    return { success: false, error: `Unsupported deCONZ cover service: ${requestArg.service}` };
  }

  private async callSensorService(targetArg: IDeconzServiceTarget, requestArg: IServiceCallRequest): Promise<IServiceCallResult> {
    if (targetArg.resource !== 'sensors') {
      return { success: false, error: 'deCONZ sensor services require a sensor-backed target.' };
    }
    if (requestArg.service !== 'set_value') {
      return { success: false, error: `Unsupported deCONZ ${requestArg.domain} service: ${requestArg.service}` };
    }

    const field = this.stringFromData(requestArg.data, ['field', 'attribute']) || targetArg.configKey || targetArg.stateKey;
    if (!field) {
      return { success: false, error: 'deCONZ set_value requires a field, attribute, or mapped sensor key.' };
    }

    const value = this.valueFromData(requestArg.data, ['value', 'temperature', 'target_temperature']);
    if (value === undefined) {
      return { success: false, error: 'deCONZ set_value requires a value.' };
    }

    if (targetArg.configKey || this.isSensorConfigField(field)) {
      await this.client.setSensorConfig(targetArg.resourceId, { [field]: this.normalizeSensorConfigValue(field, value) } as IDeconzSensorConfigPatch);
    } else {
      await this.client.setSensorState(targetArg.resourceId, { [field]: value } as IDeconzSensorStatePatch);
    }
    return { success: true };
  }

  private lightPayload(requestArg: IServiceCallRequest): IDeconzLightStatePatch | IDeconzGroupActionPatch | undefined {
    if (requestArg.service === 'turn_off') {
      return { on: false };
    }
    if (requestArg.service === 'turn_on') {
      const payload: IDeconzLightStatePatch = { on: true };
      const brightness = this.numberFromData(requestArg.data, ['brightness']);
      const brightnessPct = this.numberFromData(requestArg.data, ['brightness_pct', 'percentage']);
      const transition = this.numberFromData(requestArg.data, ['transition']);
      const colorTemp = this.numberFromData(requestArg.data, ['color_temp', 'color_temp_mired']);
      if (brightness !== undefined) {
        payload.bri = this.clamp(Math.round(brightness), 0, 255);
      } else if (brightnessPct !== undefined) {
        payload.bri = this.percentToBri(brightnessPct);
      }
      if (transition !== undefined) {
        payload.transitiontime = Math.round(transition * 10);
      }
      if (colorTemp !== undefined) {
        payload.ct = Math.round(colorTemp);
      }
      if (Array.isArray(requestArg.data?.xy_color)) {
        payload.xy = requestArg.data.xy_color as number[];
      }
      return payload;
    }
    if (requestArg.service === 'set_percentage') {
      const percentage = this.numberFromData(requestArg.data, ['percentage']);
      if (percentage === undefined) {
        return undefined;
      }
      return {
        on: percentage > 0,
        bri: this.percentToBri(percentage),
      };
    }
    return undefined;
  }

  private async resolveTarget(requestArg: IServiceCallRequest): Promise<IDeconzServiceTarget | undefined> {
    const entities = DeconzMapper.toEntities(await this.client.getSnapshot());
    const entity = entities.find((entityArg) => entityArg.id === requestArg.target.entityId)
      || entities.find((entityArg) => entityArg.deviceId === requestArg.target.deviceId && entityArg.platform === requestArg.domain);
    if (!entity) {
      return undefined;
    }

    const resource = entity.attributes?.deconzResource;
    const resourceId = entity.attributes?.deconzId;
    if (typeof resource !== 'string' || typeof resourceId !== 'string') {
      return undefined;
    }
    return {
      entity,
      resource,
      resourceId,
      stateKey: typeof entity.attributes?.deconzStateKey === 'string' ? entity.attributes.deconzStateKey : undefined,
      configKey: typeof entity.attributes?.deconzConfigKey === 'string' ? entity.attributes.deconzConfigKey : undefined,
    };
  }

  private numberFromData(dataArg: Record<string, unknown> | undefined, keysArg: string[]): number | undefined {
    const value = this.valueFromData(dataArg, keysArg);
    return typeof value === 'number' && Number.isFinite(value) ? value : undefined;
  }

  private stringFromData(dataArg: Record<string, unknown> | undefined, keysArg: string[]): string | undefined {
    const value = this.valueFromData(dataArg, keysArg);
    return typeof value === 'string' && value ? value : undefined;
  }

  private valueFromData(dataArg: Record<string, unknown> | undefined, keysArg: string[]): unknown {
    for (const key of keysArg) {
      if (dataArg && key in dataArg) {
        return dataArg[key];
      }
    }
    return undefined;
  }

  private isSensorConfigField(fieldArg: string): boolean {
    return new Set(['battery', 'coolsetpoint', 'fanmode', 'heatsetpoint', 'locked', 'mode', 'offset', 'on', 'preset', 'reachable', 'setvalve']).has(fieldArg);
  }

  private normalizeSensorConfigValue(fieldArg: string, valueArg: unknown): unknown {
    if ((fieldArg === 'heatsetpoint' || fieldArg === 'coolsetpoint') && typeof valueArg === 'number' && Math.abs(valueArg) < 100) {
      return Math.round(valueArg * 100);
    }
    return valueArg;
  }

  private percentToBri(valueArg: number): number {
    return this.clamp(Math.round(valueArg / 100 * 255), 0, 255);
  }

  private clamp(valueArg: number, minArg: number, maxArg: number): number {
    return Math.min(maxArg, Math.max(minArg, valueArg));
  }
}
