import * as plugins from '../../plugins.js';
import { SimpleLocalClient, type IServiceCallResult, type ISimpleLocalEntitySnapshot, type ISimpleLocalSnapshot } from '../../core/index.js';
import type { ILinuxBatteryConfig } from './linux_battery.types.js';
import { linuxBatteryProfile } from './linux_battery.types.js';

const defaultPowerSupplyPath = '/sys/class/power_supply';
const linuxDefaultBattery = 1;

type TLinuxBatteryFieldValue = string | number;
type TLinuxBatteryFields = Record<string, TLinuxBatteryFieldValue>;

export class LinuxBatteryClient extends SimpleLocalClient<ILinuxBatteryConfig> {
  private currentLiveSnapshot?: ISimpleLocalSnapshot;

  constructor(configArg: ILinuxBatteryConfig) {
    super(linuxBatteryProfile, configArg);
    this.localConfig = configArg;
  }

  private readonly localConfig: ILinuxBatteryConfig;

  public override async getSnapshot(forceRefreshArg = false): Promise<ISimpleLocalSnapshot> {
    if (this.shouldUseSimpleLocal()) {
      return super.getSnapshot(forceRefreshArg);
    }

    if (!forceRefreshArg && this.currentLiveSnapshot) {
      return clone(this.currentLiveSnapshot);
    }

    try {
      const snapshot = await this.readSysfsSnapshot();
      this.currentLiveSnapshot = snapshot;
      return clone(snapshot);
    } catch (errorArg) {
      const snapshot = this.fileOfflineSnapshot(errorMessage(errorArg));
      this.currentLiveSnapshot = snapshot;
      return clone(snapshot);
    }
  }

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

  private shouldUseSimpleLocal(): boolean {
    return Boolean(
      this.localConfig.snapshot
      || this.localConfig.rawData !== undefined
      || this.localConfig.rawText !== undefined
      || this.localConfig.entities?.length
      || this.localConfig.client
      || this.localConfig.snapshotProvider,
    );
  }

  private async readSysfsSnapshot(): Promise<ISimpleLocalSnapshot> {
    const batteryPath = this.batteryDirectory();
    const fields = await this.readBatteryFields(batteryPath);
    const capacity = numberValue(fields.capacity);
    if (capacity === undefined) {
      throw new Error(`Linux Battery sysfs path ${batteryPath} does not expose a numeric capacity file.`);
    }

    const batteryName = stringValue(fields.name) || this.localConfig.name || linuxBatteryProfile.defaultName || linuxBatteryProfile.displayName;
    const serialNumber = stringValue(fields.serial_number);
    const hostId = this.localConfig.deviceId || this.localConfig.uniqueId || serialNumber || batteryName || batteryPath;
    const isAndroid = (this.localConfig.system || 'linux') === 'android';
    const attributes = cleanAttributes({
      name: stringValue(fields.name),
      path: batteryPath,
      status: stringValue(fields.status),
      health: isAndroid ? stringValue(fields.health) : undefined,
      alarm: fields.alarm,
      capacity: fields.capacity,
      capacity_level: stringValue(fields.capacity_level),
      cycle_count: fields.cycle_count,
      energy_full: fields.energy_full,
      energy_full_design: fields.energy_full_design,
      energy_now: fields.energy_now,
      manufacturer: stringValue(fields.manufacturer),
      model_name: stringValue(fields.model_name),
      power_now: fields.power_now,
      serial_number: serialNumber,
      voltage_min_design: fields.voltage_min_design,
      voltage_now: fields.voltage_now,
      system: this.localConfig.system || 'linux',
    });
    const entities: ISimpleLocalEntitySnapshot[] = [{
      id: 'capacity',
      uniqueId: `${linuxBatteryProfile.domain}_${slug(hostId)}_capacity`,
      name: this.localConfig.name || batteryName,
      platform: 'sensor',
      state: capacity,
      available: true,
      unit: '%',
      deviceClass: 'battery',
      stateClass: 'measurement',
      attributes,
    }];

    return {
      device: {
        id: String(hostId),
        name: this.localConfig.name || batteryName,
        manufacturer: stringValue(fields.manufacturer) || linuxBatteryProfile.manufacturer,
        model: stringValue(fields.model_name) || linuxBatteryProfile.model,
        serialNumber,
        protocol: 'local',
        attributes: cleanAttributes({ path: batteryPath, system: this.localConfig.system || 'linux' }),
      },
      entities,
      online: true,
      updatedAt: new Date().toISOString(),
      source: 'client',
      rawData: { path: batteryPath, fields },
      capabilities: {
        localRead: true,
        localControl: false,
        filePolling: true,
      },
    };
  }

  private batteryDirectory(): string {
    const configuredBatteryPath = stringValue(this.localConfig.batteryPath);
    const rootPath = stringValue(this.localConfig.sysfsPath) || stringValue(this.localConfig.path) || defaultPowerSupplyPath;
    if (configuredBatteryPath) {
      return plugins.path.isAbsolute(configuredBatteryPath) ? configuredBatteryPath : plugins.path.join(rootPath, configuredBatteryPath);
    }
    if ((this.localConfig.system || 'linux') === 'android') {
      return plugins.path.join(rootPath, 'battery');
    }
    return plugins.path.join(rootPath, `BAT${this.localConfig.battery || linuxDefaultBattery}`);
  }

  private async readBatteryFields(batteryPathArg: string): Promise<TLinuxBatteryFields> {
    const entries = await plugins.fs.readdir(batteryPathArg, { withFileTypes: true });
    const fields: TLinuxBatteryFields = {};
    await Promise.all(entries.filter((entryArg) => entryArg.isFile()).map(async (entryArg) => {
      const value = await plugins.fs.readFile(plugins.path.join(batteryPathArg, entryArg.name), 'utf8');
      fields[entryArg.name] = parseFieldValue(value);
    }));
    return fields;
  }

  private fileOfflineSnapshot(errorArg: string): ISimpleLocalSnapshot {
    return {
      device: {
        id: this.localConfig.uniqueId || this.localConfig.deviceId || this.localConfig.name || linuxBatteryProfile.domain,
        name: this.localConfig.name || linuxBatteryProfile.defaultName || linuxBatteryProfile.displayName,
        manufacturer: linuxBatteryProfile.manufacturer,
        model: linuxBatteryProfile.model,
        protocol: 'local',
        attributes: cleanAttributes({ path: this.batteryDirectory(), system: this.localConfig.system || 'linux' }),
      },
      entities: [],
      online: false,
      updatedAt: new Date().toISOString(),
      source: 'runtime',
      error: errorArg,
      capabilities: {
        localRead: true,
        localControl: false,
        filePolling: true,
      },
    };
  }
}

const parseFieldValue = (valueArg: string): TLinuxBatteryFieldValue => {
  const trimmed = valueArg.trim();
  if (/^-?\d+(?:\.\d+)?$/.test(trimmed)) {
    return Number(trimmed);
  }
  return trimmed;
};

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

const slug = (valueArg: unknown): string => String(valueArg || '').toLowerCase().replace(/[^a-z0-9]+/g, '_').replace(/^_+|_+$/g, '') || 'device';

const clone = <TValue>(valueArg: TValue): TValue => JSON.parse(JSON.stringify(valueArg)) as TValue;

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