import * as plugins from '../../plugins.js';
import { SimpleLocalClient, SimpleLocalMapper, type IServiceCallRequest, type IServiceCallResult } from '../../core/index.js';
import type { IKefConfig, IKefSnapshot, TKefSpeakerType } from './kef.types.js';
import { kefDefaultName, kefDefaultPort, kefDefaultTimeoutMs, kefProfile } from './kef.types.js';

type TKefSource = 'Wifi' | 'Bluetooth' | 'Aux' | 'Opt' | 'Usb';
type TKefOrientation = 'L/R' | 'R/L';
type TKefStandbyTime = 20 | 60 | null;

interface IKefState {
  source: string;
  isOn: boolean;
  standbyTime: TKefStandbyTime;
  orientation: TKefOrientation;
}

interface IKefVolumeState {
  raw: number;
  level: number;
  muted: boolean;
}

interface IKefModeState {
  desk_mode: boolean | 'Unknown';
  wall_mode: boolean | 'Unknown';
  phase_correction: boolean | 'Unknown';
  high_pass: boolean | 'Unknown';
  sub_polarity: '-' | '+' | 'Unknown';
  bass_extension: 'Less' | 'Standard' | 'Extra' | 'Unknown';
}

const kefCommands = {
  getVolume: Buffer.from([0x47, 0x25, 0x80]),
  setVolume: (valueArg: number) => Buffer.from([0x53, 0x25, 0x81, valueArg]),
  getSource: Buffer.from([0x47, 0x30, 0x80]),
  setSource: (valueArg: number) => Buffer.from([0x53, 0x30, 0x81, valueArg]),
  setPlayPause: Buffer.from([0x53, 0x31, 0x81, 0x81]),
  nextTrack: Buffer.from([0x53, 0x31, 0x81, 0x82]),
  previousTrack: Buffer.from([0x53, 0x31, 0x81, 0x83]),
  getMode: Buffer.from([0x47, 0x27, 0x80]),
  setMode: (valueArg: number) => Buffer.from([0x53, 0x27, 0x81, valueArg]),
} as const;

const kefSetOkResponse = 17;
const kefSourceBaseCodes: Record<TKefSource, number> = {
  Bluetooth: 9,
  Aux: 10,
  Opt: 11,
  Usb: 12,
  Wifi: 2,
};
const kefStandbyOptions: TKefStandbyTime[] = [20, 60, null];
const kefDspCodes = {
  desk_db: 40,
  wall_db: 41,
  treble_db: 42,
  high_hz: 43,
  low_hz: 44,
  sub_db: 45,
} as const;
const kefDspOptions = {
  desk_db: range(-6, 0, 0.5),
  wall_db: range(-6, 0, 0.5),
  treble_db: range(-2, 2, 0.5),
  high_hz: range(50, 120, 5),
  low_hz: range(40, 250, 5),
  sub_db: range(-10, 10, 1),
} as const;
const kefBassExtensionByBits: Record<string, IKefModeState['bass_extension']> = {
  '00': 'Standard',
  '10': 'Less',
  '01': 'Extra',
  '11': 'Unknown',
};
const kefBassExtensionToBits: Record<'Less' | 'Standard' | 'Extra', string> = {
  Standard: '00',
  Less: '10',
  Extra: '01',
};
const kefSourceResponses = buildSourceResponses();

export class KefClient extends SimpleLocalClient<IKefConfig> {
  private kefCurrentSnapshot?: IKefSnapshot;

  constructor(private readonly configArg: IKefConfig) {
    super(kefProfile, configArg);
  }

  public async getSnapshot(forceRefreshArg = false): Promise<IKefSnapshot> {
    if (!forceRefreshArg && this.kefCurrentSnapshot) {
      return SimpleLocalMapper.clone(this.kefCurrentSnapshot) as IKefSnapshot;
    }

    if (!forceRefreshArg && this.hasStaticSnapshotSource()) {
      this.kefCurrentSnapshot = await super.getSnapshot(false) as IKefSnapshot;
      return SimpleLocalMapper.clone(this.kefCurrentSnapshot) as IKefSnapshot;
    }

    if (this.configArg.client || this.configArg.snapshotProvider) {
      this.kefCurrentSnapshot = await super.getSnapshot(forceRefreshArg) as IKefSnapshot;
      return SimpleLocalMapper.clone(this.kefCurrentSnapshot) as IKefSnapshot;
    }

    if (this.configArg.host && this.configArg.transport !== 'snapshot') {
      try {
        this.kefCurrentSnapshot = await this.readLiveSnapshot();
      } catch (errorArg) {
        this.kefCurrentSnapshot = this.offlineKefSnapshot(this.errorMessage(errorArg));
      }
      return SimpleLocalMapper.clone(this.kefCurrentSnapshot) as IKefSnapshot;
    }

    this.kefCurrentSnapshot = await super.getSnapshot(forceRefreshArg) as IKefSnapshot;
    return SimpleLocalMapper.clone(this.kefCurrentSnapshot) as IKefSnapshot;
  }

  public async refresh(): Promise<IServiceCallResult> {
    this.kefCurrentSnapshot = undefined;
    const snapshot = await this.getSnapshot(true);
    const success = snapshot.online && !snapshot.error;
    return { success, error: success ? undefined : snapshot.error, data: snapshot };
  }

  public async execute(requestArg: IServiceCallRequest): Promise<IServiceCallResult> {
    if (!this.configArg.host || this.configArg.transport === 'snapshot') {
      return super.execute(requestArg);
    }

    try {
      const data = await this.executeLive(requestArg);
      this.kefCurrentSnapshot = undefined;
      return { success: true, data };
    } catch (errorArg) {
      return { success: false, error: this.errorMessage(errorArg) };
    }
  }

  private async readLiveSnapshot(): Promise<IKefSnapshot> {
    const [state, volume] = await Promise.all([this.getState(), this.getVolumeState()]);
    const dsp = this.configArg.includeDsp ? await this.updateDsp() : undefined;
    const name = this.configArg.name || kefDefaultName;
    const speakerType = this.speakerType();
    const rawData = {
      state,
      volume,
      dsp,
      sourceList: this.sourceList(),
    };

    return {
      device: {
        id: this.configArg.uniqueId || this.configArg.deviceId || this.configArg.macAddress || `${this.host()}:${this.port()}`,
        name,
        manufacturer: 'KEF',
        model: speakerType ? `KEF ${speakerType}` : kefProfile.model,
        serialNumber: this.configArg.macAddress || this.configArg.deviceId,
        host: this.host(),
        port: this.port(),
        protocol: 'tcp',
        configurationUrl: `tcp://${this.host()}:${this.port()}`,
        attributes: {
          speakerType,
        },
      },
      entities: [
        {
          id: 'media_player',
          uniqueId: `${kefProfile.domain}_${SimpleLocalMapper.slug(this.configArg.uniqueId || this.configArg.deviceId || this.configArg.macAddress || `${this.host()}_${this.port()}`)}_media_player`,
          name,
          platform: 'media_player',
          state: state.isOn ? 'on' : 'off',
          available: true,
          writable: true,
          attributes: {
            source: state.source,
            source_list: this.sourceList(),
            volume_level: volume.level,
            is_volume_muted: volume.muted,
            standby_time: state.standbyTime,
            orientation: state.orientation,
            dsp,
          },
        },
      ],
      online: true,
      updatedAt: new Date().toISOString(),
      source: 'tcp',
      rawData,
      capabilities: {
        localRead: true,
        localControl: true,
        liveControlRequiresExecutor: false,
      },
    };
  }

  private async executeLive(requestArg: IServiceCallRequest): Promise<unknown> {
    const service = requestArg.service;
    if (service === 'media_play' || service === 'media_pause' || service === 'set_play_pause') {
      await this.expectOk(await this.sendMessage(kefCommands.setPlayPause));
      return { command: service };
    }
    if (service === 'media_next_track' || service === 'next_track') {
      await this.expectOk(await this.sendMessage(kefCommands.nextTrack));
      return { command: service };
    }
    if (service === 'media_previous_track' || service === 'previous_track') {
      await this.expectOk(await this.sendMessage(kefCommands.previousTrack));
      return { command: service };
    }
    if (service === 'volume_up' || service === 'volume_down') {
      const volume = await this.getVolumeState();
      const nextLevel = volume.level + (service === 'volume_up' ? this.volumeStep() : -this.volumeStep());
      return this.setVolume(nextLevel);
    }
    if (service === 'volume_set' || service === 'set_volume' || service === 'set_volume_level') {
      const volumeLevel = this.numberData(requestArg, 'volume_level') ?? this.numberData(requestArg, 'volumeLevel') ?? this.numberData(requestArg, 'level');
      if (volumeLevel === undefined) {
        throw new Error('KEF volume_set requires data.volume_level.');
      }
      return this.setVolume(volumeLevel);
    }
    if (service === 'volume_mute' || service === 'mute') {
      const muted = this.booleanData(requestArg, 'is_volume_muted') ?? this.booleanData(requestArg, 'mute') ?? this.booleanData(requestArg, 'muted');
      if (muted === undefined) {
        throw new Error('KEF volume_mute requires data.is_volume_muted or data.mute.');
      }
      return this.setMuted(muted);
    }
    if (service === 'select_source' || service === 'set_source') {
      const source = this.stringData(requestArg, 'source');
      if (!source) {
        throw new Error('KEF select_source requires data.source.');
      }
      await this.setSource(source, 'on');
      return { source };
    }
    if (service === 'turn_on') {
      const state = await this.getState();
      const source = this.stringData(requestArg, 'source') || state.source;
      await this.setSource(source, 'on');
      return { source, state: 'on' };
    }
    if (service === 'turn_off') {
      const state = await this.getState();
      await this.setSource(state.source, 'off');
      return { source: state.source, state: 'off' };
    }
    if (service === 'update_dsp') {
      return this.updateDsp();
    }
    if (service === 'set_mode') {
      return this.setMode(requestArg);
    }

    const dspService = service.replace(/^set_/, '') as keyof typeof kefDspCodes;
    if (dspService in kefDspCodes) {
      const value = this.numberData(requestArg, 'db_value') ?? this.numberData(requestArg, 'hz_value') ?? this.numberData(requestArg, dspService) ?? this.numberData(requestArg, 'value');
      if (value === undefined) {
        throw new Error(`KEF ${service} requires a numeric value.`);
      }
      await this.setDsp(dspService, value);
      return { [dspService]: value };
    }

    throw new Error(`Unsupported KEF service: ${requestArg.domain}.${service}`);
  }

  private async getState(): Promise<IKefState> {
    const response = await this.sendMessage(kefCommands.getSource);
    const isOn = response <= 128;
    const code = response % 128;
    const sourceResponse = kefSourceResponses[code];
    if (!sourceResponse) {
      throw new Error(`KEF source query returned unsupported code ${response}.`);
    }
    return { ...sourceResponse, isOn };
  }

  private async getVolumeState(): Promise<IKefVolumeState> {
    const raw = await this.sendMessage(kefCommands.getVolume);
    return {
      raw,
      level: Number(((raw % 128) / 100).toFixed(2)),
      muted: raw >= 128,
    };
  }

  private async setVolume(volumeLevelArg: number): Promise<IKefVolumeState> {
    const clamped = Math.max(0, Math.min(this.maximumVolume(), volumeLevelArg));
    const raw = Math.round(clamped * 100);
    await this.expectOk(await this.sendMessage(kefCommands.setVolume(raw)));
    return { raw, level: Number((raw / 100).toFixed(2)), muted: false };
  }

  private async setMuted(mutedArg: boolean): Promise<IKefVolumeState> {
    const volume = await this.getVolumeState();
    const rawVolume = volume.raw % 128;
    const raw = mutedArg ? rawVolume + 128 : rawVolume;
    await this.expectOk(await this.sendMessage(kefCommands.setVolume(raw)));
    return { raw, level: Number((rawVolume / 100).toFixed(2)), muted: mutedArg };
  }

  private async setSource(sourceArg: string, stateArg: 'on' | 'off'): Promise<void> {
    const source = this.normalizeSource(sourceArg);
    let code = this.sourceCode(source);
    if (stateArg === 'off') {
      code += 128;
    }
    await this.expectOk(await this.sendMessage(kefCommands.setSource(code)));
  }

  private async updateDsp(): Promise<Record<string, unknown>> {
    const mode = await this.getMode();
    return {
      desk_db: await this.getDsp('desk_db'),
      wall_db: await this.getDsp('wall_db'),
      treble_db: await this.getDsp('treble_db'),
      high_hz: await this.getDsp('high_hz'),
      low_hz: await this.getDsp('low_hz'),
      sub_db: await this.getDsp('sub_db'),
      ...mode,
    };
  }

  private async getMode(): Promise<IKefModeState> {
    return bitsToMode(await this.sendMessage(kefCommands.getMode));
  }

  private async setMode(requestArg: IServiceCallRequest): Promise<IKefModeState> {
    const current = await this.getMode();
    const next: IKefModeState = {
      desk_mode: this.booleanData(requestArg, 'desk_mode') ?? current.desk_mode,
      wall_mode: this.booleanData(requestArg, 'wall_mode') ?? current.wall_mode,
      phase_correction: this.booleanData(requestArg, 'phase_correction') ?? current.phase_correction,
      high_pass: this.booleanData(requestArg, 'high_pass') ?? current.high_pass,
      sub_polarity: this.subPolarityData(requestArg) ?? current.sub_polarity,
      bass_extension: this.bassExtensionData(requestArg) ?? current.bass_extension,
    };
    await this.expectOk(await this.sendMessage(kefCommands.setMode(modeToBits(next))));
    return next;
  }

  private async getDsp(whichArg: keyof typeof kefDspCodes): Promise<number | 'Unknown'> {
    const response = await this.sendMessage(Buffer.from([0x47, kefDspCodes[whichArg], 0x80]));
    if (response === 255) {
      return 'Unknown';
    }
    const value = kefDspOptions[whichArg][response - 128];
    if (value === undefined) {
      throw new Error(`KEF ${whichArg} query returned unsupported code ${response}.`);
    }
    return value;
  }

  private async setDsp(whichArg: keyof typeof kefDspCodes, valueArg: number): Promise<void> {
    const options = kefDspOptions[whichArg];
    const index = options.findIndex((optionArg) => Number(optionArg) === Number(valueArg));
    if (index < 0) {
      throw new Error(`KEF ${whichArg} value ${valueArg} is not supported.`);
    }
    await this.expectOk(await this.sendMessage(Buffer.from([0x53, kefDspCodes[whichArg], 0x81, index + 128])));
  }

  private async sendMessage(messageArg: Buffer): Promise<number> {
    const host = this.host();
    const port = this.port();
    const timeoutMs = this.timeoutMs();

    return new Promise<number>((resolve, reject) => {
      const socket = plugins.net.createConnection({ host, port });
      let buffer = Buffer.alloc(0);
      let settled = false;

      const finish = (errorArg?: Error, valueArg?: number) => {
        if (settled) {
          return;
        }
        settled = true;
        socket.removeAllListeners();
        socket.destroy();
        if (errorArg) {
          reject(errorArg);
          return;
        }
        resolve(valueArg ?? 0);
      };

      socket.setTimeout(timeoutMs, () => finish(new Error(`KEF TCP request to ${host}:${port} timed out after ${timeoutMs}ms.`)));
      socket.on('connect', () => socket.write(messageArg));
      socket.on('data', (chunkArg) => {
        buffer = Buffer.concat([buffer, Buffer.isBuffer(chunkArg) ? chunkArg : Buffer.from(chunkArg)]);
        const parsed = parseKefResponse(messageArg, buffer);
        if (parsed !== undefined) {
          finish(undefined, parsed);
        }
      });
      socket.on('end', () => finish(new Error(`KEF TCP connection to ${host}:${port} closed before a response was received.`)));
      socket.on('close', () => finish(new Error(`KEF TCP connection to ${host}:${port} closed before a response was received.`)));
      socket.on('error', (errorArg) => finish(errorArg));
    });
  }

  private expectOk(responseArg: number): void {
    if (responseArg !== kefSetOkResponse) {
      throw new Error(`KEF command did not return OK; received ${responseArg}.`);
    }
  }

  private sourceCode(sourceArg: TKefSource): number {
    const standbyIndex = kefStandbyOptions.findIndex((valueArg) => valueArg === this.standbyTime());
    return kefSourceBaseCodes[sourceArg] + standbyIndex * 16 + (this.configArg.inverseSpeakerMode ? 64 : 0);
  }

  private normalizeSource(sourceArg: string): TKefSource {
    const match = this.sourceList().find((sourceItemArg) => sourceItemArg.toLowerCase() === sourceArg.toLowerCase());
    if (!match) {
      throw new Error(`Unsupported KEF source: ${sourceArg}.`);
    }
    return match as TKefSource;
  }

  private sourceList(): TKefSource[] {
    return this.speakerType() === 'LS50' ? ['Wifi', 'Bluetooth', 'Aux', 'Opt', 'Usb'] : ['Wifi', 'Bluetooth', 'Aux', 'Opt'];
  }

  private speakerType(): TKefSpeakerType | undefined {
    return this.configArg.speakerType || this.configArg.type;
  }

  private standbyTime(): TKefStandbyTime {
    return this.configArg.standbyTime === undefined ? null : this.configArg.standbyTime;
  }

  private maximumVolume(): number {
    return this.configArg.maximumVolume ?? this.configArg.maxVolume ?? 0.5;
  }

  private volumeStep(): number {
    return this.configArg.volumeStep ?? 0.05;
  }

  private host(): string {
    const host = this.configArg.host;
    if (!host) {
      throw new Error('KEF TCP client requires config.host.');
    }
    return host;
  }

  private port(): number {
    return this.configArg.port || kefDefaultPort;
  }

  private timeoutMs(): number {
    return this.configArg.timeoutMs || kefDefaultTimeoutMs;
  }

  private hasStaticSnapshotSource(): boolean {
    return Boolean(this.configArg.snapshot || this.configArg.rawData !== undefined || this.configArg.rawText !== undefined || this.configArg.entities?.length);
  }

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

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

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

  private subPolarityData(requestArg: IServiceCallRequest): '-' | '+' | undefined {
    const value = this.stringData(requestArg, 'sub_polarity');
    return value === '-' || value === '+' ? value : undefined;
  }

  private bassExtensionData(requestArg: IServiceCallRequest): 'Less' | 'Standard' | 'Extra' | undefined {
    const value = this.stringData(requestArg, 'bass_extension');
    return value === 'Less' || value === 'Standard' || value === 'Extra' ? value : undefined;
  }

  private offlineKefSnapshot(errorArg: string): IKefSnapshot {
    return SimpleLocalMapper.toSnapshot({ profile: kefProfile, config: this.configArg, online: false, source: 'runtime', error: errorArg }) as IKefSnapshot;
  }

  private errorMessage(errorArg: unknown): string {
    return errorArg instanceof Error ? errorArg.message : String(errorArg);
  }
}

function range(startArg: number, endArg: number, stepArg: number): number[] {
  const values: number[] = [];
  for (let value = startArg; value <= endArg; value += stepArg) {
    values.push(Number(value.toFixed(3)));
  }
  return values;
}

function buildSourceResponses(): Record<number, { source: string; standbyTime: TKefStandbyTime; orientation: TKefOrientation }> {
  const responses: Record<number, { source: string; standbyTime: TKefStandbyTime; orientation: TKefOrientation }> = {};
  const sources: Record<string, number> = { ...kefSourceBaseCodes, Bluetooth_paired: 15 };
  for (const [source, baseCode] of Object.entries(sources)) {
    for (const [standbyIndex, standbyTime] of kefStandbyOptions.entries()) {
      const lrCode = baseCode + standbyIndex * 16;
      responses[lrCode] = { source: source.replace('_paired', ''), standbyTime, orientation: 'L/R' };
      responses[lrCode + 64] = { source: source.replace('_paired', ''), standbyTime, orientation: 'R/L' };
    }
  }
  responses[48] = responses[82];
  return responses;
}

function parseKefResponse(messageArg: Buffer, replyArg: Buffer): number | undefined {
  const responses = splitKefResponses(replyArg);
  if (messageArg[0] === 0x53) {
    return responses.some((responseArg) => responseArg.length >= 3 && responseArg[0] === 0x52 && responseArg[1] === kefSetOkResponse && responseArg[2] === 0xff)
      ? kefSetOkResponse
      : undefined;
  }

  if (messageArg[0] !== 0x47) {
    throw new Error(`Unknown KEF command prefix ${messageArg[0]}.`);
  }
  const which = messageArg[1];
  const response = responses.find((responseArg) => responseArg.length >= 3 && responseArg[1] === which);
  if (!response) {
    return undefined;
  }
  return response.length >= 4 ? response[response.length - 2] : response[2];
}

function splitKefResponses(replyArg: Buffer): Buffer[] {
  const starts: number[] = [];
  for (let index = 0; index < replyArg.length; index++) {
    if (replyArg[index] === 0x52) {
      starts.push(index);
    }
  }
  return starts.map((startArg, indexArg) => replyArg.subarray(startArg, starts[indexArg + 1] ?? replyArg.length));
}

function bitsToMode(bitsArg: number): IKefModeState {
  if (bitsArg === 255) {
    return {
      desk_mode: 'Unknown',
      wall_mode: 'Unknown',
      phase_correction: 'Unknown',
      high_pass: 'Unknown',
      sub_polarity: 'Unknown',
      bass_extension: 'Unknown',
    };
  }
  const bits = bitsArg.toString(2).padStart(8, '0');
  return {
    desk_mode: bits[7] === '1',
    wall_mode: bits[6] === '1',
    phase_correction: bits[5] === '1',
    high_pass: bits[4] === '1',
    sub_polarity: bits[1] === '1' ? '-' : '+',
    bass_extension: kefBassExtensionByBits[bits.slice(2, 4)],
  };
}

function modeToBits(modeArg: IKefModeState): number {
  if (Object.values(modeArg).includes('Unknown')) {
    throw new Error('KEF set_mode requires known current mode values; run it while the speaker reports DSP mode state.');
  }
  const boolBit = (valueArg: boolean | 'Unknown') => valueArg === true ? '1' : '0';
  const subPolarity = modeArg.sub_polarity === '-' ? '1' : '0';
  const bassExtension = kefBassExtensionToBits[modeArg.bass_extension as 'Less' | 'Standard' | 'Extra'];
  return Number.parseInt(`1${subPolarity}${bassExtension}${boolBit(modeArg.high_pass)}${boolBit(modeArg.phase_correction)}${boolBit(modeArg.wall_mode)}${boolBit(modeArg.desk_mode)}`, 2);
}
