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 { BleboxClient } from './blebox.classes.client.js';
import { BleboxConfigFlow } from './blebox.classes.configflow.js';
import { createBleboxDiscoveryDescriptor } from './blebox.discovery.js';
import { BleboxMapper } from './blebox.mapper.js';
import type { IBleboxConfig, IBleboxCoverFeature, IBleboxLightFeature, IBleboxLightStatePatch, IBleboxSnapshot, IBleboxSwitchFeature, TBleboxCoverCommand, TBleboxFeature } from './blebox.types.js';

export class BleboxIntegration extends BaseIntegration<IBleboxConfig> {
  public readonly domain = 'blebox';
  public readonly displayName = 'BleBox devices';
  public readonly status = 'control-runtime' as const;
  public readonly discoveryDescriptor = createBleboxDiscoveryDescriptor();
  public readonly configFlow = new BleboxConfigFlow();
  public readonly metadata = {
    source: 'home-assistant/core',
    upstreamPath: 'homeassistant/components/blebox',
    upstreamDomain: 'blebox',
    integrationType: 'device',
    iotClass: 'local_polling',
    requirements: ['blebox-uniapi==2.5.2'],
    codeowners: ['@bbx-a', '@swistakm', '@bkobus-bbx'],
  };

  public async setup(configArg: IBleboxConfig, contextArg: IIntegrationSetupContext): Promise<IIntegrationRuntime> {
    void contextArg;
    return new BleboxRuntime(new BleboxClient(configArg));
  }

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

export class HomeAssistantBleboxIntegration extends BleboxIntegration {}

class BleboxRuntime implements IIntegrationRuntime {
  public domain = 'blebox';

  constructor(private readonly client: BleboxClient) {}

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

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

  public async callService(requestArg: IServiceCallRequest): Promise<IServiceCallResult> {
    const snapshot = await this.client.getSnapshot();
    if (requestArg.domain === 'switch') {
      return this.callSwitchService(snapshot, requestArg);
    }
    if (requestArg.domain === 'light') {
      return this.callLightService(snapshot, requestArg);
    }
    if (requestArg.domain === 'cover') {
      return this.callCoverService(snapshot, requestArg);
    }
    return { success: false, error: `Unsupported BleBox service domain: ${requestArg.domain}` };
  }

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

  private async callSwitchService(snapshotArg: IBleboxSnapshot, requestArg: IServiceCallRequest): Promise<IServiceCallResult> {
    if (!['turn_on', 'turn_off'].includes(requestArg.service)) {
      return { success: false, error: `Unsupported BleBox switch service: ${requestArg.service}` };
    }
    const feature = this.resolveFeature(snapshotArg, requestArg, 'switch') as IBleboxSwitchFeature | undefined;
    if (!feature) {
      return { success: false, error: 'BleBox switch service calls require a switch entity target, or an unambiguous switch device target.' };
    }
    await this.client.setSwitchState(feature.unitId, requestArg.service === 'turn_on', snapshotArg);
    return { success: true };
  }

  private async callLightService(snapshotArg: IBleboxSnapshot, requestArg: IServiceCallRequest): Promise<IServiceCallResult> {
    if (!['turn_on', 'turn_off'].includes(requestArg.service)) {
      return { success: false, error: `Unsupported BleBox light service: ${requestArg.service}` };
    }
    const feature = this.resolveFeature(snapshotArg, requestArg, 'light') as IBleboxLightFeature | undefined;
    if (!feature) {
      return { success: false, error: 'BleBox light service calls require a light entity target, or an unambiguous light device target.' };
    }

    const patch = this.lightPatch(requestArg, requestArg.service === 'turn_on');
    if ('error' in patch) {
      return { success: false, error: patch.error };
    }
    await this.client.setLightState(patch, snapshotArg);
    return { success: true };
  }

  private async callCoverService(snapshotArg: IBleboxSnapshot, requestArg: IServiceCallRequest): Promise<IServiceCallResult> {
    const supported = ['open_cover', 'close_cover', 'stop_cover', 'set_cover_position', 'open_cover_tilt', 'close_cover_tilt', 'set_cover_tilt_position'];
    if (!supported.includes(requestArg.service)) {
      return { success: false, error: `Unsupported BleBox cover service: ${requestArg.service}` };
    }
    const feature = this.resolveFeature(snapshotArg, requestArg, 'cover') as IBleboxCoverFeature | undefined;
    if (!feature) {
      return { success: false, error: 'BleBox cover service calls require a cover entity target, or an unambiguous cover device target.' };
    }

    const position = requestArg.service === 'set_cover_position' ? requestArg.data?.position : undefined;
    const tiltPosition = requestArg.service === 'set_cover_tilt_position' ? requestArg.data?.tilt_position : undefined;
    if (requestArg.service === 'set_cover_position' && !this.isPercent(position)) {
      return { success: false, error: 'BleBox set_cover_position requires data.position as an integer between 0 and 100.' };
    }
    if (requestArg.service === 'set_cover_tilt_position' && !this.isPercent(tiltPosition)) {
      return { success: false, error: 'BleBox set_cover_tilt_position requires data.tilt_position as an integer between 0 and 100.' };
    }

    await this.client.runCoverCommand({
      command: requestArg.service as TBleboxCoverCommand,
      position: typeof position === 'number' ? position : undefined,
      tiltPosition: typeof tiltPosition === 'number' ? tiltPosition : undefined,
    }, snapshotArg);
    return { success: true };
  }

  private resolveFeature(snapshotArg: IBleboxSnapshot, requestArg: IServiceCallRequest, platformArg: TBleboxFeature['platform']): TBleboxFeature | undefined {
    const features = BleboxMapper.features(snapshotArg).filter((featureArg) => featureArg.platform === platformArg);
    if (requestArg.target.entityId) {
      const entities = BleboxMapper.toEntities(snapshotArg);
      const entity = entities.find((entityArg) => entityArg.id === requestArg.target.entityId);
      return features.find((featureArg) => featureArg.uniqueId === entity?.uniqueId);
    }
    if (requestArg.target.deviceId) {
      const deviceFeatures = requestArg.target.deviceId === BleboxMapper.deviceId(snapshotArg) ? features : [];
      return deviceFeatures.length === 1 ? deviceFeatures[0] : undefined;
    }
    return undefined;
  }

  private lightPatch(requestArg: IServiceCallRequest, onArg: boolean): IBleboxLightStatePatch | { error: string } {
    const brightness = requestArg.data?.brightness;
    if (brightness !== undefined && !this.isByte(brightness)) {
      return { error: 'BleBox light brightness must be an integer between 0 and 255.' };
    }
    const rgbColor = this.colorTuple(requestArg.data?.rgb_color, 3, 'rgb_color');
    if ('error' in rgbColor) return rgbColor;
    const rgbwColor = this.colorTuple(requestArg.data?.rgbw_color, 4, 'rgbw_color');
    if ('error' in rgbwColor) return rgbwColor;
    const rgbwwColor = this.colorTuple(requestArg.data?.rgbww_color, 5, 'rgbww_color');
    if ('error' in rgbwwColor) return rgbwwColor;
    const colorTempKelvin = requestArg.data?.color_temp_kelvin;
    if (colorTempKelvin !== undefined && (typeof colorTempKelvin !== 'number' || !Number.isFinite(colorTempKelvin) || colorTempKelvin <= 0)) {
      return { error: 'BleBox light color_temp_kelvin must be a positive number.' };
    }
    const effect = requestArg.data?.effect;
    if (effect !== undefined && typeof effect !== 'string' && typeof effect !== 'number') {
      return { error: 'BleBox light effect must be a string name or numeric effect id.' };
    }

    return {
      on: onArg,
      brightness: typeof brightness === 'number' ? brightness : undefined,
      rgbColor: rgbColor.value as [number, number, number] | undefined,
      rgbwColor: rgbwColor.value as [number, number, number, number] | undefined,
      rgbwwColor: rgbwwColor.value as [number, number, number, number, number] | undefined,
      colorTempKelvin: typeof colorTempKelvin === 'number' ? colorTempKelvin : undefined,
      effect: typeof effect === 'string' || typeof effect === 'number' ? effect : undefined,
    };
  }

  private colorTuple(valueArg: unknown, lengthArg: number, nameArg: string): { value?: number[] } | { error: string } {
    if (valueArg === undefined) {
      return {};
    }
    if (!Array.isArray(valueArg) || valueArg.length !== lengthArg || valueArg.some((entryArg) => !this.isByte(entryArg))) {
      return { error: `BleBox light ${nameArg} must be an array of ${lengthArg} integers between 0 and 255.` };
    }
    return { value: valueArg as number[] };
  }

  private isByte(valueArg: unknown): valueArg is number {
    return typeof valueArg === 'number' && Number.isInteger(valueArg) && valueArg >= 0 && valueArg <= 255;
  }

  private isPercent(valueArg: unknown): valueArg is number {
    return typeof valueArg === 'number' && Number.isInteger(valueArg) && valueArg >= 0 && valueArg <= 100;
  }
}
