import { SimpleLocalClient, SimpleLocalMapper, type IServiceCallRequest, type IServiceCallResult, type ISimpleLocalEntitySnapshot, type ISimpleLocalSnapshot } from '../../core/index.js';
import type { ILupusecConfig } from './lupusec.types.js';
import { lupusecProfile } from './lupusec.types.js';

type TLupusecModel = 1 | 2;

interface ILupusecRawSnapshot {
  model: TLupusecModel;
  panel?: Record<string, unknown>;
  sensors: Array<Record<string, unknown>>;
  powerSwitches: Array<Record<string, unknown>>;
  fetchedAt: string;
}

const xt2ModeMap: Record<string, string> = {
  '{AREA_MODE_0}': 'Disarm',
  '{AREA_MODE_1}': 'Arm',
  '{AREA_MODE_2}': 'Home',
  '{AREA_MODE_3}': 'Home',
  '{AREA_MODE_4}': 'Home',
};

const switchTypes = new Set<unknown>(['Steckdose', 24, 25, '24', '25']);
const openingTypes = new Set<unknown>(['Türkontakt', 'Fensterkontakt', 4, '4']);
const smokeTypes = new Set<unknown>(['Rauchmelder', 11, '11']);
const waterTypes = new Set<unknown>(['Wassermelder', 5, '5']);

export class LupusecClient extends SimpleLocalClient<ILupusecConfig> {
  private liveSnapshot?: ISimpleLocalSnapshot;

  constructor(configArg: ILupusecConfig) {
    super(lupusecProfile, configArg);
    this.localConfig = configArg;
  }

  private readonly localConfig: ILupusecConfig;

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

    if (this.hasLiveHttpConfig()) {
      try {
        this.liveSnapshot = await this.fetchSnapshot();
      } catch (errorArg) {
        this.liveSnapshot = this.makeOfflineSnapshot(this.errorMessage(errorArg));
      }
      return SimpleLocalMapper.clone(this.liveSnapshot);
    }

    return super.getSnapshot(forceRefreshArg);
  }

  public async refresh(): Promise<IServiceCallResult> {
    this.liveSnapshot = 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.localConfig.client?.execute || this.localConfig.commandExecutor) {
      return super.execute(requestArg);
    }
    if (requestArg.service === 'refresh' || requestArg.service === 'status' || requestArg.service === 'snapshot') {
      const snapshot = await this.getSnapshot(requestArg.service === 'refresh');
      return { success: snapshot.online && !snapshot.error, error: snapshot.error, data: snapshot };
    }
    return {
      success: false,
      error: 'LUPUSEC native HTTP support is read-only: HA/lupupy exposes panel and device read endpoints, but no documented local power-switch write endpoint is implemented here. Provide an injected client.execute() or commandExecutor for live writes.',
    };
  }

  private async fetchSnapshot(): Promise<ISimpleLocalSnapshot> {
    const model = await this.resolveModel();
    const token = model === 2 ? await this.requestToken() : undefined;
    const panelData = this.recordValue(await this.requestAction('panelCondGet', { token }));
    const sensorsData = this.recordValue(await this.requestAction(model === 1 ? 'sensorListGet' : 'deviceListGet', { token }));
    const powerSwitchesData = model === 1 ? await this.requestAction('pssStatusGet', { token }).catch(() => undefined) : undefined;
    const rawData: ILupusecRawSnapshot = {
      model,
      panel: this.recordValue(panelData.updates) || panelData,
      sensors: this.listFromContainer(sensorsData, 'senrows'),
      powerSwitches: this.powerSwitches(powerSwitchesData),
      fetchedAt: new Date().toISOString(),
    };

    return this.snapshotFromRaw(rawData);
  }

  private snapshotFromRaw(rawDataArg: ILupusecRawSnapshot): ISimpleLocalSnapshot {
    const panelModeRaw = rawDataArg.panel?.[rawDataArg.model === 1 ? 'mode_st' : 'mode_a1'] ?? rawDataArg.panel?.mode;
    const panelMode = this.modeValue(panelModeRaw, rawDataArg.model);
    const entities: ISimpleLocalEntitySnapshot[] = [];

    if (panelMode !== undefined) {
      entities.push({
        id: 'alarm_mode',
        name: 'Alarm Mode',
        platform: 'sensor',
        state: panelMode,
        available: true,
        attributes: { deviceType: 'Alarm', model: rawDataArg.model },
      });
    }

    for (const sensor of rawDataArg.sensors) {
      entities.push(this.entityFromDevice(sensor, false));
    }
    for (const powerSwitch of rawDataArg.powerSwitches) {
      entities.push(this.entityFromDevice(powerSwitch, true));
    }

    return {
      device: {
        id: this.localConfig.uniqueId || this.localConfig.deviceId || this.localConfig.host || 'lupusec',
        name: this.localConfig.name || `LUPUSEC XT${rawDataArg.model}`,
        manufacturer: 'Lupus Electronics',
        model: `LUPUSEC XT${rawDataArg.model}`,
        host: this.localConfig.host || this.urlHost(),
        port: this.localConfig.port,
        protocol: this.localConfig.useTls ? 'https' : 'http',
        configurationUrl: this.originUrl(),
        attributes: { model: rawDataArg.model },
      },
      entities,
      online: true,
      updatedAt: rawDataArg.fetchedAt,
      source: 'http',
      rawData: rawDataArg,
      capabilities: {
        localRead: true,
        localControl: Boolean(this.localConfig.client?.execute || this.localConfig.commandExecutor),
        nativeControl: false,
        liveControlRequiresExecutor: true,
      },
    };
  }

  private entityFromDevice(deviceArg: Record<string, unknown>, forceSwitchArg: boolean): ISimpleLocalEntitySnapshot {
    const id = this.stringValue(deviceArg.device_id) || this.stringValue(deviceArg.sid) || this.stringValue(deviceArg.no) || SimpleLocalMapper.slug(deviceArg.name);
    const type = deviceArg.type;
    const platform = forceSwitchArg || switchTypes.has(type) ? 'switch' : 'binary_sensor';
    const status = deviceArg.status ?? deviceArg.openClose ?? deviceArg.cond ?? deviceArg.pssonoff;
    return {
      id,
      uniqueId: `${lupusecProfile.domain}_${id}`,
      name: this.stringValue(deviceArg.name) || `LUPUSEC ${id}`,
      platform,
      state: platform === 'switch' ? this.switchState(status) : this.binaryState(status),
      available: this.stringValue(status) !== 'offline',
      writable: false,
      deviceClass: platform === 'binary_sensor' ? this.deviceClass(type) : undefined,
      attributes: {
        deviceId: id,
        deviceType: type,
        status,
        area: deviceArg.area,
        room: deviceArg.room,
        faults: deviceArg.faults,
      },
    };
  }

  private async resolveModel(): Promise<TLupusecModel> {
    if (this.localConfig.model === 1 || this.localConfig.model === 2) {
      return this.localConfig.model;
    }
    const response = await globalThis.fetch(this.resourceUrl('/images/model.gif'), {
      method: 'GET',
      signal: AbortSignal.timeout(this.localConfig.timeoutMs || 5000),
    });
    return response.status === 200 ? 1 : 2;
  }

  private async requestToken(): Promise<string | undefined> {
    const response = this.recordValue(await this.requestAction('tokenGet', {}));
    return this.stringValue(response.message);
  }

  private async requestAction(actionArg: string, optionsArg: { token?: string; method?: 'GET' | 'POST'; params?: Record<string, string | number | boolean | undefined> }): Promise<unknown> {
    const headers: Record<string, string> = { accept: 'application/json, text/plain;q=0.9, */*;q=0.8' };
    if (this.localConfig.username) {
      headers.authorization = `Basic ${Buffer.from(`${this.localConfig.username}:${this.localConfig.password || ''}`).toString('base64')}`;
    }
    if (optionsArg.token) {
      headers['x-token'] = optionsArg.token;
    }

    let body: URLSearchParams | undefined;
    if (optionsArg.method === 'POST') {
      body = new URLSearchParams();
      for (const [key, value] of Object.entries(optionsArg.params || {})) {
        if (value !== undefined) {
          body.set(key, String(value));
        }
      }
      headers['content-type'] = 'application/x-www-form-urlencoded';
    }

    const response = await globalThis.fetch(this.actionUrl(actionArg), {
      method: optionsArg.method || 'GET',
      headers,
      body,
      signal: AbortSignal.timeout(this.localConfig.timeoutMs || 5000),
    });
    const text = await response.text();
    if (!response.ok) {
      throw new Error(`LUPUSEC ${actionArg} failed with HTTP ${response.status}: ${text.slice(0, 160)}`);
    }
    return this.parseResponse(text);
  }

  private parseResponse(textArg: string): unknown {
    const trimmed = textArg.trim();
    if (!trimmed) {
      return {};
    }
    try {
      return JSON.parse(trimmed) as unknown;
    } catch {}

    const newline = trimmed.indexOf('\n');
    const xt1Payload = newline >= 0 ? trimmed.slice(newline + 1).replace(/[;\s]*$/g, '') : trimmed;
    try {
      return JSON.parse(xt1Payload) as unknown;
    } catch {}

    throw new Error('LUPUSEC response was not JSON. XT1 YAML-style payloads require an injected snapshotProvider until a no-dependency YAML parser is available.');
  }

  private powerSwitches(valueArg: unknown): Array<Record<string, unknown>> {
    const container = this.recordValue(valueArg);
    const forms = this.recordValue(container.forms);
    if (!forms) {
      return [];
    }
    return Object.values(forms)
      .filter((value): value is Record<string, unknown> => this.isRecord(value))
      .map((value, index) => ({ ...value, device_id: this.stringValue(value.device_id) || String(index + 1), type: value.type ?? 'Steckdose', status: value.status ?? value.pssonoff }));
  }

  private listFromContainer(containerArg: Record<string, unknown>, keyArg: string): Array<Record<string, unknown>> {
    const value = containerArg[keyArg];
    if (Array.isArray(value)) {
      return value.filter((itemArg): itemArg is Record<string, unknown> => this.isRecord(itemArg));
    }
    const record = this.recordValue(value);
    if (record) {
      return Object.values(record).filter((itemArg): itemArg is Record<string, unknown> => this.isRecord(itemArg));
    }
    return [];
  }

  private modeValue(valueArg: unknown, modelArg: TLupusecModel): string | undefined {
    const value = this.stringValue(valueArg);
    if (!value) {
      return undefined;
    }
    return modelArg === 2 ? xt2ModeMap[value] || value : value;
  }

  private switchState(valueArg: unknown): boolean {
    if (typeof valueArg === 'boolean') {
      return valueArg;
    }
    if (typeof valueArg === 'number') {
      return valueArg === 0;
    }
    const value = this.stringValue(valueArg).toLowerCase();
    return value === '0' || value === 'on' || value === 'true';
  }

  private binaryState(valueArg: unknown): boolean {
    if (typeof valueArg === 'boolean') {
      return valueArg;
    }
    if (typeof valueArg === 'number') {
      return valueArg === 1;
    }
    const value = this.stringValue(valueArg).toLowerCase();
    return value === '1' || value === 'offen' || value === '{web_msg_dc_open}' || value === 'open' || value === 'true';
  }

  private deviceClass(typeArg: unknown): string | undefined {
    if (openingTypes.has(typeArg)) {
      return 'door';
    }
    if (smokeTypes.has(typeArg)) {
      return 'smoke';
    }
    if (waterTypes.has(typeArg)) {
      return 'moisture';
    }
    return undefined;
  }

  private makeOfflineSnapshot(errorArg: string): ISimpleLocalSnapshot {
    return SimpleLocalMapper.toSnapshot({ profile: lupusecProfile, config: this.localConfig, online: false, source: 'runtime', error: errorArg });
  }

  private hasLiveHttpConfig(): boolean {
    return Boolean((this.localConfig.host || this.localConfig.url) && this.localConfig.username && this.localConfig.password);
  }

  private actionUrl(actionArg: string): string {
    return this.resourceUrl(`/action/${actionArg}`);
  }

  private resourceUrl(pathArg: string): string {
    if (this.localConfig.url) {
      const base = new URL(this.localConfig.url);
      return `${base.protocol}//${base.host}${pathArg}`;
    }
    const protocol = this.localConfig.useTls ? 'https' : 'http';
    const port = this.localConfig.port && !((protocol === 'https' && this.localConfig.port === 443) || (protocol === 'http' && this.localConfig.port === 80)) ? `:${this.localConfig.port}` : '';
    return `${protocol}://${this.localConfig.host}${port}${pathArg}`;
  }

  private originUrl(): string | undefined {
    if (!this.localConfig.host && !this.localConfig.url) {
      return undefined;
    }
    return this.resourceUrl('/');
  }

  private urlHost(): string | undefined {
    if (!this.localConfig.url) {
      return undefined;
    }
    return new URL(this.localConfig.url).hostname;
  }

  private recordValue(valueArg: unknown): Record<string, unknown> {
    return valueArg && typeof valueArg === 'object' && !Array.isArray(valueArg) ? valueArg as Record<string, unknown> : {};
  }

  private isRecord(valueArg: unknown): valueArg is Record<string, unknown> {
    return Boolean(valueArg && typeof valueArg === 'object' && !Array.isArray(valueArg));
  }

  private stringValue(valueArg: unknown): string {
    return typeof valueArg === 'string' ? valueArg : valueArg === undefined || valueArg === null ? '' : String(valueArg);
  }

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