import * as plugins from '../../plugins.js';
import type { IIntegrationEntity, IIntegrationEvent, IServiceCallRequest } from '../../core/types.js';
import type {
  IRainbirdCommand,
  IRainbirdEvent,
  IRainbirdProgram,
  IRainbirdSnapshot,
  IRainbirdZone,
} from './rainbird.types.js';

const rainbirdDomain = 'rainbird';
const defaultDurationMinutes = 6;

export class RainbirdMapper {
  public static toDevices(snapshotArg: IRainbirdSnapshot): plugins.shxInterfaces.data.IDeviceDefinition[] {
    const updatedAt = snapshotArg.updatedAt || new Date().toISOString();
    const controllerDeviceId = this.controllerDeviceId(snapshotArg);
    const devices: plugins.shxInterfaces.data.IDeviceDefinition[] = [{
      id: controllerDeviceId,
      integrationDomain: rainbirdDomain,
      name: snapshotArg.controller.name || 'Rain Bird Controller',
      protocol: 'http',
      manufacturer: snapshotArg.controller.manufacturer || 'Rain Bird',
      model: snapshotArg.controller.modelName || snapshotArg.controller.modelCode || snapshotArg.controller.modelId,
      online: snapshotArg.connected,
      features: [
        { id: 'connectivity', capability: 'sensor', name: 'Connectivity', readable: true, writable: false },
        { id: 'rain_sensor', capability: 'sensor', name: 'Rain sensor', readable: true, writable: false },
        { id: 'rain_delay', capability: 'sensor', name: 'Rain delay', readable: true, writable: true, unit: 'd' },
      ],
      state: [
        { featureId: 'connectivity', value: snapshotArg.connected ? 'online' : 'offline', updatedAt },
        { featureId: 'rain_sensor', value: snapshotArg.controller.rainSensorActive ?? null, updatedAt },
        { featureId: 'rain_delay', value: snapshotArg.controller.rainDelayDays ?? null, updatedAt },
      ],
      metadata: this.cleanAttributes({
        host: snapshotArg.controller.host,
        port: snapshotArg.controller.port,
        protocol: snapshotArg.controller.protocol,
        serialNumber: snapshotArg.controller.serialNumber,
        macAddress: snapshotArg.controller.macAddress,
        modelId: snapshotArg.controller.modelId,
        modelCode: snapshotArg.controller.modelCode,
        firmwareVersion: snapshotArg.controller.firmwareVersion,
        protocolRevision: snapshotArg.controller.protocolRevision,
        maxPrograms: snapshotArg.controller.maxPrograms,
        maxStations: snapshotArg.controller.maxStations,
        supportsWaterBudget: snapshotArg.controller.supportsWaterBudget,
      }),
    }];

    for (const zone of snapshotArg.zones) {
      devices.push({
        id: this.zoneDeviceId(snapshotArg, zone),
        integrationDomain: rainbirdDomain,
        name: zone.name || `Sprinkler ${zone.id}`,
        protocol: 'http',
        manufacturer: 'Rain Bird',
        model: 'Irrigation zone',
        online: snapshotArg.connected && zone.available !== false,
        features: [
          { id: 'irrigation', capability: 'switch', name: 'Irrigation', readable: true, writable: true },
          { id: 'default_duration', capability: 'sensor', name: 'Default duration', readable: true, writable: true, unit: 'min' },
        ],
        state: [
          { featureId: 'irrigation', value: zone.active ?? false, updatedAt },
          { featureId: 'default_duration', value: zone.defaultDurationMinutes ?? defaultDurationMinutes, updatedAt },
        ],
        metadata: this.cleanAttributes({
          zoneId: zone.id,
          viaDevice: controllerDeviceId,
          remainingRuntimeSeconds: zone.remainingRuntimeSeconds,
          lastStartedAt: zone.lastStartedAt,
          lastStoppedAt: zone.lastStoppedAt,
          ...zone.attributes,
        }),
      });
    }

    for (const program of snapshotArg.programs) {
      devices.push({
        id: this.programDeviceId(snapshotArg, program),
        integrationDomain: rainbirdDomain,
        name: program.name || `Program ${program.id}`,
        protocol: 'http',
        manufacturer: 'Rain Bird',
        model: 'Irrigation program',
        online: snapshotArg.connected && program.enabled !== false,
        features: [
          { id: 'program_state', capability: 'sensor', name: 'Program state', readable: true, writable: false },
          { id: 'duration', capability: 'sensor', name: 'Duration', readable: true, writable: false, unit: 'min' },
        ],
        state: [
          { featureId: 'program_state', value: program.enabled === false ? 'disabled' : 'scheduled', updatedAt },
          { featureId: 'duration', value: this.programDuration(program), updatedAt },
        ],
        metadata: this.cleanAttributes({
          programId: program.id,
          viaDevice: controllerDeviceId,
          frequency: program.frequency,
          starts: program.starts,
          daysOfWeek: program.daysOfWeek,
          nextStartAt: program.nextStartAt,
          zoneDurations: program.zoneDurations,
          ...program.attributes,
        }),
      });
    }

    return devices;
  }

  public static toEntities(snapshotArg: IRainbirdSnapshot): IIntegrationEntity[] {
    const entities: IIntegrationEntity[] = [];
    const usedIds = new Map<string, number>();
    const controllerDeviceId = this.controllerDeviceId(snapshotArg);
    const uniqueBase = this.uniqueBase(snapshotArg);

    entities.push(this.entity('binary_sensor', 'Rainsensor', controllerDeviceId, `rainbird_${uniqueBase}_rainsensor`, snapshotArg.controller.rainSensorActive ? 'on' : 'off', usedIds, {
      deviceClass: 'moisture',
    }, snapshotArg.connected));

    entities.push(this.entity('sensor', 'Raindelay', controllerDeviceId, `rainbird_${uniqueBase}_raindelay`, snapshotArg.controller.rainDelayDays ?? null, usedIds, {
      unit: 'd',
      deviceClass: 'duration',
    }, snapshotArg.connected));

    entities.push(this.entity('number', 'Rain delay', controllerDeviceId, `rainbird_${uniqueBase}_rain_delay`, snapshotArg.controller.rainDelayDays ?? null, usedIds, {
      unit: 'd',
      min: 0,
      max: 14,
      step: 1,
    }, snapshotArg.connected));

    for (const zone of snapshotArg.zones) {
      entities.push(this.entity('switch', zone.name || `Sprinkler ${zone.id}`, this.zoneDeviceId(snapshotArg, zone), `rainbird_${uniqueBase}_zone_${zone.id}`, zone.active ? 'on' : 'off', usedIds, {
        zoneId: zone.id,
        defaultDurationMinutes: zone.defaultDurationMinutes ?? defaultDurationMinutes,
        remainingRuntimeSeconds: zone.remainingRuntimeSeconds,
        ...zone.attributes,
      }, snapshotArg.connected && zone.available !== false));
    }

    for (const program of snapshotArg.programs) {
      entities.push(this.entity('sensor', program.name || `Program ${program.id}`, this.programDeviceId(snapshotArg, program), `rainbird_${uniqueBase}_program_${this.slug(String(program.id))}`, program.enabled === false ? 'disabled' : 'scheduled', usedIds, {
        programId: program.id,
        frequency: program.frequency,
        starts: program.starts,
        daysOfWeek: program.daysOfWeek,
        periodDays: program.periodDays,
        synchroDays: program.synchroDays,
        durationMinutes: this.programDuration(program),
        zoneDurations: program.zoneDurations,
        nextStartAt: program.nextStartAt,
        ...program.attributes,
      }, snapshotArg.connected && program.enabled !== false));
    }

    return entities;
  }

  public static commandForService(snapshotArg: IRainbirdSnapshot, requestArg: IServiceCallRequest): IRainbirdCommand | undefined {
    if (requestArg.domain === rainbirdDomain && ['refresh', 'reload'].includes(requestArg.service)) {
      return { type: 'refresh' };
    }

    if (requestArg.domain === rainbirdDomain && ['start_zone', 'start_irrigation'].includes(requestArg.service)) {
      const zone = this.findZone(snapshotArg, requestArg);
      const duration = this.durationValue(requestArg, zone) ?? defaultDurationMinutes;
      return zone && duration > 0 ? { type: 'start_zone', zoneId: zone.id, durationMinutes: duration, entityId: requestArg.target.entityId, deviceId: requestArg.target.deviceId } : undefined;
    }

    if (requestArg.domain === 'switch' && requestArg.service === 'turn_on') {
      const zone = this.findZone(snapshotArg, requestArg);
      const duration = this.durationValue(requestArg, zone) ?? defaultDurationMinutes;
      return zone ? { type: 'start_zone', zoneId: zone.id, durationMinutes: duration, entityId: requestArg.target.entityId, deviceId: requestArg.target.deviceId } : undefined;
    }

    if ((requestArg.domain === rainbirdDomain && ['stop_zone', 'stop_irrigation'].includes(requestArg.service)) || (requestArg.domain === 'switch' && requestArg.service === 'turn_off')) {
      const zone = this.findZone(snapshotArg, requestArg);
      return { type: 'stop_zone', zoneId: zone?.id, entityId: requestArg.target.entityId, deviceId: requestArg.target.deviceId };
    }

    if (requestArg.domain === rainbirdDomain && requestArg.service === 'set_rain_delay') {
      const days = this.numberData(requestArg, 'days') ?? this.numberData(requestArg, 'delayDays') ?? this.numberData(requestArg, 'duration');
      return days !== undefined && days >= 0 && days <= 14 ? { type: 'set_rain_delay', days, entityId: requestArg.target.entityId, deviceId: requestArg.target.deviceId } : undefined;
    }

    if (requestArg.domain === 'number' && requestArg.service === 'set_value') {
      const days = this.numberData(requestArg, 'value');
      const target = requestArg.target.entityId || '';
      const isRainDelayTarget = this.toEntities(snapshotArg).some((entityArg) => entityArg.id === target && entityArg.uniqueId.endsWith('_rain_delay'));
      return isRainDelayTarget && days !== undefined && days >= 0 && days <= 14 ? { type: 'set_rain_delay', days, entityId: requestArg.target.entityId, deviceId: requestArg.target.deviceId } : undefined;
    }

    if (requestArg.domain === rainbirdDomain && requestArg.service === 'start_program') {
      const programId = this.numberData(requestArg, 'programId') ?? this.numberData(requestArg, 'program');
      return programId !== undefined ? { type: 'start_program', programId } : undefined;
    }

    if (requestArg.domain === rainbirdDomain && requestArg.service === 'raw_local_rpc') {
      const method = this.stringData(requestArg, 'method');
      return method ? { type: 'raw_local_rpc', method, params: this.recordData(requestArg, 'params') || {} } : undefined;
    }

    return undefined;
  }

  public static toIntegrationEvent(eventArg: IRainbirdEvent): IIntegrationEvent {
    return {
      type: eventArg.type === 'command_failed' ? 'error' : 'state_changed',
      integrationDomain: rainbirdDomain,
      deviceId: eventArg.deviceId,
      entityId: eventArg.entityId,
      data: eventArg,
      timestamp: eventArg.timestamp,
    };
  }

  public static controllerDeviceId(snapshotArg: IRainbirdSnapshot): string {
    return `rainbird.controller.${this.uniqueBase(snapshotArg)}`;
  }

  private static zoneDeviceId(snapshotArg: IRainbirdSnapshot, zoneArg: IRainbirdZone): string {
    return `rainbird.zone.${this.uniqueBase(snapshotArg)}.${zoneArg.id}`;
  }

  private static programDeviceId(snapshotArg: IRainbirdSnapshot, programArg: IRainbirdProgram): string {
    return `rainbird.program.${this.uniqueBase(snapshotArg)}.${this.slug(String(programArg.id))}`;
  }

  private static entity(platformArg: IIntegrationEntity['platform'], nameArg: string, deviceIdArg: string, uniqueIdArg: string, stateArg: unknown, usedIdsArg: Map<string, number>, attributesArg: Record<string, unknown>, availableArg: boolean): IIntegrationEntity {
    const baseId = `${platformArg}.${this.slug(nameArg) || this.slug(uniqueIdArg)}`;
    const seen = usedIdsArg.get(baseId) || 0;
    usedIdsArg.set(baseId, seen + 1);
    return {
      id: seen ? `${baseId}_${seen + 1}` : baseId,
      uniqueId: uniqueIdArg,
      integrationDomain: rainbirdDomain,
      deviceId: deviceIdArg,
      platform: platformArg,
      name: nameArg,
      state: stateArg,
      attributes: this.cleanAttributes(attributesArg),
      available: availableArg,
    };
  }

  private static findZone(snapshotArg: IRainbirdSnapshot, requestArg: IServiceCallRequest): IRainbirdZone | undefined {
    const explicit = this.numberData(requestArg, 'zoneId') ?? this.numberData(requestArg, 'zone') ?? this.numberData(requestArg, 'station');
    if (explicit !== undefined) {
      return snapshotArg.zones.find((zoneArg) => zoneArg.id === explicit);
    }
    const target = requestArg.target.entityId || requestArg.target.deviceId;
    if (!target) {
      return snapshotArg.zones[0];
    }
    const entity = this.toEntities(snapshotArg).find((entityArg) => entityArg.id === target || entityArg.uniqueId === target || entityArg.deviceId === target);
    const zoneId = typeof entity?.attributes?.zoneId === 'number' ? entity.attributes.zoneId : undefined;
    return snapshotArg.zones.find((zoneArg) => zoneArg.id === zoneId) || snapshotArg.zones.find((zoneArg) => this.zoneDeviceId(snapshotArg, zoneArg) === target);
  }

  private static durationValue(requestArg: IServiceCallRequest, zoneArg?: IRainbirdZone): number | undefined {
    return this.numberData(requestArg, 'durationMinutes') ?? this.numberData(requestArg, 'duration') ?? this.numberData(requestArg, 'minutes') ?? zoneArg?.defaultDurationMinutes;
  }

  private static programDuration(programArg: IRainbirdProgram): number | null {
    if (typeof programArg.durationMinutes === 'number') {
      return programArg.durationMinutes;
    }
    if (programArg.zoneDurations?.length) {
      return programArg.zoneDurations.reduce((sumArg, zoneArg) => sumArg + zoneArg.durationMinutes, 0);
    }
    return null;
  }

  private static uniqueBase(snapshotArg: IRainbirdSnapshot): string {
    return this.slug(snapshotArg.controller.macAddress || snapshotArg.controller.serialNumber || snapshotArg.controller.id || snapshotArg.controller.host || snapshotArg.controller.name || 'rainbird');
  }

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

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

  private static 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 static cleanAttributes(attributesArg: Record<string, unknown>): Record<string, unknown> {
    return Object.fromEntries(Object.entries(attributesArg).filter(([, valueArg]) => valueArg !== undefined));
  }

  private static slug(valueArg: string): string {
    return valueArg.toLowerCase().replace(/[^a-z0-9]+/g, '_').replace(/^_+|_+$/g, '') || 'rainbird';
  }
}
