import { SimpleLocalClient } from '../../core/index.js';
import { LinksysSmartMapper } from './linksys_smart.mapper.js';
import type { ILinksysSmartConfig, ILinksysSmartConnectedDevice, ILinksysSmartJnapDevice, ILinksysSmartJnapResponse, ILinksysSmartLiveRawData } from './linksys_smart.types.js';
import { linksysSmartDefaultPort, linksysSmartProfile } from './linksys_smart.types.js';

const defaultTimeoutMs = 10000;
const transactionAction = 'http://linksys.com/jnap/core/Transaction';
const getDevicesAction = 'http://linksys.com/jnap/devicelist/GetDevices';

export class LinksysSmartClient extends SimpleLocalClient<ILinksysSmartConfig> {
  constructor(private readonly linksysConfig: ILinksysSmartConfig) {
    super(linksysSmartProfile, linksysConfig);
  }

  public override async getSnapshot(forceRefreshArg = false) {
    if (this.linksysConfig.client || this.linksysConfig.snapshotProvider || (!forceRefreshArg && this.linksysConfig.snapshot)) {
      return super.getSnapshot(forceRefreshArg);
    }
    if (!this.hasLiveTarget()) {
      return super.getSnapshot(forceRefreshArg);
    }
    try {
      const rawData = await this.getRawData();
      return LinksysSmartMapper.toSnapshot({ config: this.linksysConfig, rawData, online: true, source: 'http' });
    } catch (errorArg) {
      return LinksysSmartMapper.toSnapshot({
        config: this.linksysConfig,
        rawData: { fetchedAt: new Date().toISOString(), devices: [] },
        online: false,
        source: 'http',
        error: errorArg instanceof Error ? errorArg.message : String(errorArg),
      });
    }
  }

  public async getRawData(): Promise<ILinksysSmartLiveRawData> {
    const rawResponse = await this.getDevicesResponse();
    return {
      devices: this.connectedDevices(rawResponse),
      rawResponse,
      fetchedAt: new Date().toISOString(),
    };
  }

  public async getDevices(): Promise<ILinksysSmartConnectedDevice[]> {
    return (await this.getRawData()).devices || [];
  }

  public async getDevicesResponse(): Promise<ILinksysSmartJnapResponse> {
    const controller = new AbortController();
    const timeout = setTimeout(() => controller.abort(), this.linksysConfig.timeoutMs || defaultTimeoutMs);
    try {
      const response = await globalThis.fetch(this.jnapUrl(), {
        method: 'POST',
        headers: {
          accept: 'application/json',
          'content-type': 'application/json',
          'x-jnap-action': transactionAction,
        },
        body: JSON.stringify([{ request: { sinceRevision: 0 }, action: getDevicesAction }]),
        signal: controller.signal,
      });
      const text = await response.text();
      if (!response.ok) {
        throw new Error(`Linksys Smart Wi-Fi JNAP GetDevices failed with HTTP ${response.status}: ${text.slice(0, 160)}`);
      }
      return text ? JSON.parse(text) as ILinksysSmartJnapResponse : {};
    } finally {
      clearTimeout(timeout);
    }
  }

  private connectedDevices(responseArg: ILinksysSmartJnapResponse): ILinksysSmartConnectedDevice[] {
    const devices = responseArg.responses?.[0]?.output?.devices || [];
    const connected: ILinksysSmartConnectedDevice[] = [];
    for (const device of devices) {
      const macAddress = this.macAddress(device);
      if (!macAddress || !Array.isArray(device.connections) || device.connections.length === 0) {
        continue;
      }
      connected.push({
        macAddress,
        name: this.deviceName(device),
        deviceId: device.deviceID,
        rawDevice: device,
      });
    }
    return connected;
  }

  private macAddress(deviceArg: ILinksysSmartJnapDevice): string | undefined {
    const macs = Array.isArray(deviceArg.knownMACAddresses) ? deviceArg.knownMACAddresses.filter((valueArg) => typeof valueArg === 'string' && valueArg.trim()) : [];
    return macs.length ? macs[macs.length - 1] : undefined;
  }

  private deviceName(deviceArg: ILinksysSmartJnapDevice): string {
    const userName = deviceArg.properties?.find((propertyArg) => propertyArg.name === 'userDeviceName')?.value;
    return userName || deviceArg.friendlyName || deviceArg.deviceID || this.macAddress(deviceArg) || 'Unknown device';
  }

  private jnapUrl(): string {
    if (this.linksysConfig.url) {
      const url = new URL(this.linksysConfig.url);
      if (!url.pathname || url.pathname === '/') {
        url.pathname = '/JNAP/';
      }
      return url.toString();
    }
    if (!this.linksysConfig.host) {
      throw new Error('Linksys Smart Wi-Fi host or url is required for JNAP polling.');
    }
    const port = this.linksysConfig.port || linksysSmartDefaultPort;
    return `http://${this.linksysConfig.host}${port === 80 ? '' : `:${port}`}/JNAP/`;
  }

  private hasLiveTarget(): boolean {
    return Boolean(this.linksysConfig.host || this.linksysConfig.url);
  }
}
