import * as plugins from '../../plugins.js';
import { SimpleLocalClient, type IServiceCallRequest, type IServiceCallResult } from '../../core/index.js';
import { MochadMapper } from './mochad.mapper.js';
import type { IMochadConfig, IMochadDeviceConfig, IMochadDeviceState, IMochadSnapshot, TMochadCommType, TMochadDevicePlatform } from './mochad.types.js';
import { mochadDefaultPort, mochadDefaultTimeoutMs, mochadProfile } from './mochad.types.js';

export class MochadClient extends SimpleLocalClient<IMochadConfig> {
  constructor(private readonly configArg: IMochadConfig) {
    super(mochadProfile, configArg);
  }

  public async getSnapshot(forceRefreshArg = false): Promise<IMochadSnapshot> {
    if (this.shouldUseLiveTcp()) {
      try {
        return await this.liveSnapshot();
      } catch (errorArg) {
        return MochadMapper.toSnapshot({
          config: this.configArg,
          online: false,
          source: 'runtime',
          error: errorMessage(errorArg),
        });
      }
    }

    return super.getSnapshot(forceRefreshArg) as Promise<IMochadSnapshot>;
  }

  public async execute(requestArg: IServiceCallRequest): Promise<IServiceCallResult> {
    if (this.configArg.client?.execute || this.configArg.commandExecutor) {
      return super.execute(requestArg);
    }

    if (!this.configArg.host) {
      return {
        success: false,
        error: 'Mochad live commands require config.host for the mochad TCP daemon or an injected client.execute()/commandExecutor.',
      };
    }

    const target = this.targetDevice(requestArg);
    if (!target) {
      return {
        success: false,
        error: 'Mochad commands require data.address, a matching entity target, or exactly one configured device.',
      };
    }

    const command = requestArg.service === 'toggle' ? await this.toggleCommand(target) : this.commandForRequest(target, requestArg);
    if (!command) {
      return { success: false, error: `Unsupported Mochad service: ${requestArg.domain}.${requestArg.service}` };
    }

    try {
      const response = await this.sendCommand(`${this.commType(target)} ${target.address.toLowerCase()} ${command}`);
      return { success: true, data: { address: target.address, command, response } };
    } catch (errorArg) {
      return { success: false, error: errorMessage(errorArg) };
    }
  }

  public async controllerStatus(): Promise<string> {
    return this.sendCommand('st');
  }

  public async getDeviceStatus(addressArg: string): Promise<string> {
    return this.sendCommand(`getstatus ${addressArg.toLowerCase()}`);
  }

  private async liveSnapshot(): Promise<IMochadSnapshot> {
    const devices = this.configuredDevices();
    if (!devices.length) {
      const status = await this.controllerStatus();
      return MochadMapper.toSnapshot({
        config: this.configArg,
        online: true,
        source: 'tcp',
        rawData: {
          device: this.deviceInfo(),
          status,
          controllerStatus: status,
        },
      });
    }

    const states: IMochadDeviceState[] = [];
    for (const device of devices) {
      const rawStatus = await this.getDeviceStatus(device.address);
      states.push({
        ...device,
        rawStatus,
        state: this.parseOnOff(rawStatus),
      });
    }

    return MochadMapper.toSnapshot({
      config: this.configArg,
      online: true,
      source: 'tcp',
      rawData: {
        device: this.deviceInfo(),
        devices: states,
        entities: states.map((deviceArg) => ({
          id: this.entityKey(deviceArg),
          name: deviceArg.name || this.defaultDeviceName(deviceArg),
          platform: this.platform(deviceArg),
          state: deviceArg.state === 'on',
          writable: true,
          attributes: {
            address: deviceArg.address,
            commType: this.commType(deviceArg),
            rawStatus: deviceArg.rawStatus,
          },
        })),
      },
    });
  }

  private async sendCommand(commandArg: string): Promise<string> {
    const host = this.configArg.host;
    if (!host) {
      throw new Error('Mochad TCP command requires config.host.');
    }
    const port = this.configArg.port || mochadDefaultPort;
    const timeoutMs = this.configArg.timeoutMs || mochadDefaultTimeoutMs;

    return new Promise<string>((resolve, reject) => {
      const chunks: Buffer[] = [];
      let settled = false;
      const socket = plugins.net.createConnection({ host, port });

      const finish = (errorArg?: Error) => {
        if (settled) {
          return;
        }
        settled = true;
        socket.removeAllListeners();
        socket.destroy();
        if (errorArg) {
          reject(errorArg);
          return;
        }
        resolve(Buffer.concat(chunks).toString('utf8'));
      };

      socket.setTimeout(timeoutMs, () => chunks.length ? finish() : finish(new Error(`Mochad TCP command timed out after ${timeoutMs}ms.`)));
      socket.on('connect', () => socket.write(commandArg.endsWith('\n') ? commandArg : `${commandArg}\n`));
      socket.on('data', (chunkArg) => {
        const chunk = Buffer.isBuffer(chunkArg) ? chunkArg : Buffer.from(chunkArg);
        chunks.push(chunk);
        if (chunk.includes(10)) {
          finish();
        }
      });
      socket.on('end', () => finish());
      socket.on('close', () => finish());
      socket.on('error', (errorArg) => finish(errorArg));
    });
  }

  private configuredDevices(): IMochadDeviceConfig[] {
    if (this.configArg.devices?.length) {
      return this.configArg.devices.filter((deviceArg) => Boolean(deviceArg.address));
    }
    if (this.configArg.address) {
      return [{ address: this.configArg.address, name: this.configArg.name, commType: this.configArg.commType || this.configArg.comm_type }];
    }
    return [];
  }

  private targetDevice(requestArg: IServiceCallRequest): IMochadDeviceConfig | undefined {
    const devices = this.configuredDevices();
    const address = stringValue(requestArg.data?.address);
    if (address) {
      return { ...devices.find((deviceArg) => deviceArg.address.toLowerCase() === address.toLowerCase()), address };
    }
    if (devices.length === 1) {
      return devices[0];
    }
    const entityId = stringValue(requestArg.target.entityId);
    if (!entityId) {
      return undefined;
    }
    return devices.find((deviceArg) => entityId.endsWith(`.${this.entityKey(deviceArg)}`) || entityId.includes(MochadMapper.slug(deviceArg.name || this.defaultDeviceName(deviceArg))));
  }

  private commandForRequest(deviceArg: IMochadDeviceConfig, requestArg: IServiceCallRequest): string | undefined {
    if (requestArg.service === 'turn_on') {
      const brightness = numberValue(requestArg.data?.brightness);
      if (this.platform(deviceArg) === 'light' && brightness !== undefined && this.brightnessLevels(deviceArg) > 32) {
        return `xdim ${this.brightnessValue(deviceArg, brightness)}`;
      }
      return 'on';
    }
    if (requestArg.service === 'turn_off') {
      return 'off';
    }
    return undefined;
  }

  private async toggleCommand(deviceArg: IMochadDeviceConfig): Promise<string> {
    const status = await this.getDeviceStatus(deviceArg.address);
    return this.parseOnOff(status) === 'on' ? 'off' : 'on';
  }

  private shouldUseLiveTcp(): boolean {
    return Boolean(this.configArg.host) && !this.configArg.snapshot && this.configArg.rawData === undefined && this.configArg.rawText === undefined && !this.configArg.entities?.length && !this.configArg.client && !this.configArg.snapshotProvider;
  }

  private deviceInfo(): Record<string, unknown> {
    const host = this.configArg.host;
    const port = this.configArg.port || (host ? mochadDefaultPort : undefined);
    return {
      id: this.configArg.uniqueId || (host ? `${host}:${port}` : undefined) || this.configArg.name || mochadProfile.domain,
      name: this.configArg.name || mochadProfile.defaultName,
      manufacturer: mochadProfile.manufacturer,
      model: mochadProfile.model,
      host,
      port,
      protocol: 'tcp',
    };
  }

  private parseOnOff(valueArg: string): 'on' | 'off' | 'unknown' {
    const normalized = valueArg.trim().toLowerCase();
    if (normalized === 'on' || normalized.endsWith(' on')) {
      return 'on';
    }
    if (normalized === 'off' || normalized.endsWith(' off')) {
      return 'off';
    }
    return 'unknown';
  }

  private platform(deviceArg: IMochadDeviceConfig): TMochadDevicePlatform {
    return deviceArg.platform || 'switch';
  }

  private commType(deviceArg: IMochadDeviceConfig): TMochadCommType {
    return deviceArg.commType || deviceArg.comm_type || this.configArg.commType || this.configArg.comm_type || 'pl';
  }

  private brightnessLevels(deviceArg: IMochadDeviceConfig): number {
    return deviceArg.brightnessLevels || deviceArg.brightness_levels || 32;
  }

  private brightnessValue(deviceArg: IMochadDeviceConfig, brightnessArg: number): number {
    const levels = this.brightnessLevels(deviceArg) - 1;
    return Math.max(0, Math.min(levels, Math.round(brightnessArg * (levels / 255))));
  }

  private entityKey(deviceArg: IMochadDeviceConfig): string {
    return MochadMapper.slug(deviceArg.name || this.defaultDeviceName(deviceArg));
  }

  private defaultDeviceName(deviceArg: IMochadDeviceConfig): string {
    return `x10_${this.platform(deviceArg)}_dev_${deviceArg.address}`;
  }
}

const stringValue = (valueArg: unknown): string | undefined => typeof valueArg === 'string' && valueArg.trim() ? valueArg.trim() : undefined;

const numberValue = (valueArg: unknown): number | undefined => {
  if (typeof valueArg === 'number' && Number.isFinite(valueArg)) {
    return valueArg;
  }
  if (typeof valueArg === 'string' && valueArg.trim() && Number.isFinite(Number(valueArg))) {
    return Number(valueArg);
  }
  return undefined;
};

const errorMessage = (errorArg: unknown): string => errorArg instanceof Error ? errorArg.message : String(errorArg);
