import { SimpleLocalClient, SimpleLocalMapper, type ISimpleLocalEntitySnapshot, type ISimpleLocalSnapshot, type IServiceCallResult, type TSimpleLocalRawData } from '../../core/index.js';
import type { IP1MonitorConfig } from './p1_monitor.types.js';
import { p1MonitorDefaultName, p1MonitorDefaultPort, p1MonitorProfile } from './p1_monitor.types.js';

interface IP1MonitorEndpoint {
  host: string;
  port: number;
  baseUrl: string;
}

interface IP1MonitorLiveData extends Record<string, unknown> {
  smartmeter: Record<string, unknown>;
  phases: Record<string, unknown>;
  settings: Record<string, unknown>;
  watermeter?: Record<string, unknown>;
  raw: Record<string, unknown>;
  fetchedAt: string;
}

export class P1MonitorClient extends SimpleLocalClient<IP1MonitorConfig> {
  private cachedSnapshot?: ISimpleLocalSnapshot;

  constructor(private readonly configArg: IP1MonitorConfig) {
    super(p1MonitorProfile, configArg);
  }

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

    if (this.shouldUseNativeHttp(forceRefreshArg)) {
      try {
        this.cachedSnapshot = await this.fetchLiveSnapshot();
      } catch (errorArg) {
        this.cachedSnapshot = this.createOfflineSnapshot(errorMessage(errorArg));
      }
      return SimpleLocalMapper.clone(this.cachedSnapshot);
    }

    const snapshot = await super.getSnapshot(forceRefreshArg);
    this.cachedSnapshot = SimpleLocalMapper.toSnapshot({
      profile: p1MonitorProfile,
      config: this.configArg,
      rawData: (snapshot.rawData as TSimpleLocalRawData | undefined) ?? snapshot,
      online: snapshot.online,
      source: snapshot.source,
      error: snapshot.error,
    });
    return SimpleLocalMapper.clone(this.cachedSnapshot);
  }

  public override async refresh(): Promise<IServiceCallResult> {
    this.cachedSnapshot = undefined;
    const liveCapable = Boolean(this.endpoint()) || Boolean(this.configArg.client || this.configArg.snapshotProvider);
    const snapshot = await this.getSnapshot(liveCapable);
    const success = snapshot.online && !snapshot.error && (liveCapable || this.hasManualData());
    return { success, error: success ? undefined : snapshot.error || 'P1 Monitor refresh requires a local HTTP host, injected client, snapshotProvider, snapshot, entities, or rawData.', data: snapshot };
  }

  private shouldUseNativeHttp(forceRefreshArg: boolean): boolean {
    if (!this.endpoint() || this.configArg.client || this.configArg.snapshotProvider) {
      return false;
    }
    if (!forceRefreshArg && this.configArg.snapshot) {
      return false;
    }
    if (!forceRefreshArg && this.hasManualData()) {
      return false;
    }
    return true;
  }

  private async fetchLiveSnapshot(): Promise<ISimpleLocalSnapshot> {
    const endpoint = this.requireEndpoint();
    const [smartmeterRaw, phasesRaw, settingsRaw, watermeterRaw] = await Promise.all([
      this.requestJson('v1/smartmeter', { json: 'object', limit: '1' }),
      this.requestJson('v1/status', { json: 'object' }),
      this.requestJson('v1/configuration', { json: 'object' }),
      this.requestJson('v2/watermeter/day', { json: 'object', limit: '1' }).catch(() => undefined),
    ]);

    const liveData: IP1MonitorLiveData = {
      smartmeter: smartmeterFromApi(smartmeterRaw),
      phases: phasesFromApi(phasesRaw),
      settings: settingsFromApi(settingsRaw),
      watermeter: watermeterFromApi(watermeterRaw),
      raw: {
        smartmeter: smartmeterRaw,
        phases: phasesRaw,
        settings: settingsRaw,
        watermeter: watermeterRaw,
      },
      fetchedAt: new Date().toISOString(),
    };

    return this.snapshotFromLiveData(endpoint, liveData);
  }

  private async requestJson(pathArg: string, paramsArg: Record<string, string>): Promise<unknown> {
    const endpoint = this.requireEndpoint();
    const url = new URL(`/api/${pathArg.replace(/^\/+/, '')}`, endpoint.baseUrl);
    for (const [key, value] of Object.entries(paramsArg)) {
      url.searchParams.set(key, value);
    }

    const timeoutMs = this.configArg.timeoutMs || 10000;
    const controller = new AbortController();
    const timer = setTimeout(() => controller.abort(), timeoutMs);
    try {
      const response = await globalThis.fetch(url, {
        headers: {
          accept: 'application/json, text/plain, */*',
          'user-agent': 'PythonP1Monitor/3.2.0',
        },
        signal: controller.signal,
      });
      const text = await response.text();
      if (!response.ok) {
        throw new Error(`P1 Monitor HTTP request ${url.pathname} failed with ${response.status}: ${text.slice(0, 160)}`);
      }
      const contentType = response.headers.get('content-type') || '';
      if (!contentType.includes('application/json') && text.trim()) {
        throw new Error(`P1 Monitor returned unexpected content-type ${contentType || 'unknown'} from ${url.pathname}.`);
      }
      return JSON.parse(text) as unknown;
    } finally {
      clearTimeout(timer);
    }
  }

  private snapshotFromLiveData(endpointArg: IP1MonitorEndpoint, dataArg: IP1MonitorLiveData): ISimpleLocalSnapshot {
    const deviceId = this.configArg.uniqueId || this.configArg.deviceId || `${endpointArg.host}:${endpointArg.port}`;
    const entities = entitiesFromLiveData(dataArg);
    return {
      device: {
        id: deviceId,
        name: this.configArg.name || p1MonitorDefaultName,
        manufacturer: 'P1 Monitor',
        model: 'P1 Monitor HTTP API',
        host: endpointArg.host,
        port: endpointArg.port,
        protocol: 'http',
        configurationUrl: endpointArg.baseUrl,
      },
      entities,
      online: true,
      updatedAt: dataArg.fetchedAt,
      source: 'http',
      rawData: dataArg,
      capabilities: {
        localRead: true,
        localControl: false,
        p1MonitorRestApi: true,
      },
    };
  }

  private requireEndpoint(): IP1MonitorEndpoint {
    const endpoint = this.endpoint();
    if (!endpoint) {
      throw new Error('P1 Monitor HTTP polling requires config.host or config.url using the documented HTTP API.');
    }
    return endpoint;
  }

  private endpoint(): IP1MonitorEndpoint | undefined {
    const rawUrl = stringValue(this.configArg.url) || stringValue(this.configArg.host);
    if (!rawUrl) {
      return undefined;
    }

    try {
      const url = new URL(rawUrl.includes('://') ? rawUrl : `http://${rawUrl}`);
      if (url.protocol !== 'http:') {
        return undefined;
      }
      return endpointFromParts(url.hostname, this.configArg.port || numberValue(url.port) || p1MonitorDefaultPort);
    } catch {
      return endpointFromParts(rawUrl, this.configArg.port || p1MonitorDefaultPort);
    }
  }

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

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

const entitiesFromLiveData = (dataArg: IP1MonitorLiveData): ISimpleLocalEntitySnapshot[] => {
  const entities: ISimpleLocalEntitySnapshot[] = [];
  addEntities(entities, 'smartmeter', [
    ['gas_consumption', 'Gas Consumption', dataArg.smartmeter.gas_consumption, 'm3', 'gas', 'total_increasing'],
    ['power_consumption', 'Power Consumption', dataArg.smartmeter.power_consumption, 'W', 'power', 'measurement'],
    ['energy_consumption_high', 'Energy Consumption High', dataArg.smartmeter.energy_consumption_high, 'kWh', 'energy', 'total_increasing'],
    ['energy_consumption_low', 'Energy Consumption Low', dataArg.smartmeter.energy_consumption_low, 'kWh', 'energy', 'total_increasing'],
    ['power_production', 'Power Production', dataArg.smartmeter.power_production, 'W', 'power', 'measurement'],
    ['energy_production_high', 'Energy Production High', dataArg.smartmeter.energy_production_high, 'kWh', 'energy', 'total_increasing'],
    ['energy_production_low', 'Energy Production Low', dataArg.smartmeter.energy_production_low, 'kWh', 'energy', 'total_increasing'],
    ['energy_tariff_period', 'Energy Tariff Period', dataArg.smartmeter.energy_tariff_period, undefined, undefined, undefined],
  ]);
  addEntities(entities, 'phases', [
    ['voltage_phase_l1', 'Voltage Phase L1', dataArg.phases.voltage_phase_l1, 'V', 'voltage', 'measurement'],
    ['voltage_phase_l2', 'Voltage Phase L2', dataArg.phases.voltage_phase_l2, 'V', 'voltage', 'measurement'],
    ['voltage_phase_l3', 'Voltage Phase L3', dataArg.phases.voltage_phase_l3, 'V', 'voltage', 'measurement'],
    ['current_phase_l1', 'Current Phase L1', dataArg.phases.current_phase_l1, 'A', 'current', 'measurement'],
    ['current_phase_l2', 'Current Phase L2', dataArg.phases.current_phase_l2, 'A', 'current', 'measurement'],
    ['current_phase_l3', 'Current Phase L3', dataArg.phases.current_phase_l3, 'A', 'current', 'measurement'],
    ['power_consumed_phase_l1', 'Power Consumed Phase L1', dataArg.phases.power_consumed_phase_l1, 'W', 'power', 'measurement'],
    ['power_consumed_phase_l2', 'Power Consumed Phase L2', dataArg.phases.power_consumed_phase_l2, 'W', 'power', 'measurement'],
    ['power_consumed_phase_l3', 'Power Consumed Phase L3', dataArg.phases.power_consumed_phase_l3, 'W', 'power', 'measurement'],
    ['power_produced_phase_l1', 'Power Produced Phase L1', dataArg.phases.power_produced_phase_l1, 'W', 'power', 'measurement'],
    ['power_produced_phase_l2', 'Power Produced Phase L2', dataArg.phases.power_produced_phase_l2, 'W', 'power', 'measurement'],
    ['power_produced_phase_l3', 'Power Produced Phase L3', dataArg.phases.power_produced_phase_l3, 'W', 'power', 'measurement'],
  ]);
  addEntities(entities, 'settings', [
    ['gas_consumption_price', 'Gas Consumption Price', dataArg.settings.gas_consumption_price, 'EUR/m3', undefined, 'measurement'],
    ['energy_consumption_price_low', 'Energy Consumption Price Low', dataArg.settings.energy_consumption_price_low, 'EUR/kWh', undefined, 'measurement'],
    ['energy_consumption_price_high', 'Energy Consumption Price High', dataArg.settings.energy_consumption_price_high, 'EUR/kWh', undefined, 'measurement'],
    ['energy_production_price_low', 'Energy Production Price Low', dataArg.settings.energy_production_price_low, 'EUR/kWh', undefined, 'measurement'],
    ['energy_production_price_high', 'Energy Production Price High', dataArg.settings.energy_production_price_high, 'EUR/kWh', undefined, 'measurement'],
  ]);
  if (dataArg.watermeter) {
    addEntities(entities, 'watermeter', [
      ['consumption_day', 'Water Consumption Day', dataArg.watermeter.consumption_day, 'L', 'water', 'total_increasing'],
      ['consumption_total', 'Water Consumption Total', dataArg.watermeter.consumption_total, 'm3', 'water', 'total_increasing'],
      ['pulse_count', 'Water Pulse Count', dataArg.watermeter.pulse_count, undefined, undefined, undefined],
    ]);
  }
  return entities;
};

const addEntities = (entitiesArg: ISimpleLocalEntitySnapshot[], serviceArg: string, valuesArg: Array<[string, string, unknown, string | undefined, string | undefined, string | undefined]>): void => {
  for (const [id, name, state, unit, deviceClass, stateClass] of valuesArg) {
    entitiesArg.push({
      id,
      name,
      platform: 'sensor',
      state: state ?? null,
      unit,
      deviceClass,
      stateClass,
      available: true,
      writable: false,
      attributes: { service: serviceArg, source: 'p1monitor-api' },
    });
  }
};

const smartmeterFromApi = (valueArg: unknown): Record<string, unknown> => {
  const data = firstRecord(valueArg);
  return {
    gas_consumption: numberValue(data.CONSUMPTION_GAS_M3),
    power_consumption: numberValue(data.CONSUMPTION_W),
    energy_consumption_high: numberValue(data.CONSUMPTION_KWH_HIGH),
    energy_consumption_low: numberValue(data.CONSUMPTION_KWH_LOW),
    power_production: numberValue(data.PRODUCTION_W),
    energy_production_high: numberValue(data.PRODUCTION_KWH_HIGH),
    energy_production_low: numberValue(data.PRODUCTION_KWH_LOW),
    energy_tariff_period: String(data.TARIFCODE || '').toUpperCase() === 'P' ? 'high' : 'low',
  };
};

const phasesFromApi = (valueArg: unknown): Record<string, unknown> => {
  return {
    voltage_phase_l1: statusValue(valueArg, 103),
    voltage_phase_l2: statusValue(valueArg, 104),
    voltage_phase_l3: statusValue(valueArg, 105),
    current_phase_l1: statusValue(valueArg, 100),
    current_phase_l2: statusValue(valueArg, 101),
    current_phase_l3: statusValue(valueArg, 102),
    power_consumed_phase_l1: wattsValue(statusValue(valueArg, 74)),
    power_consumed_phase_l2: wattsValue(statusValue(valueArg, 75)),
    power_consumed_phase_l3: wattsValue(statusValue(valueArg, 76)),
    power_produced_phase_l1: wattsValue(statusValue(valueArg, 77)),
    power_produced_phase_l2: wattsValue(statusValue(valueArg, 78)),
    power_produced_phase_l3: wattsValue(statusValue(valueArg, 79)),
  };
};

const settingsFromApi = (valueArg: unknown): Record<string, unknown> => {
  return {
    gas_consumption_price: configurationValue(valueArg, 15),
    energy_consumption_price_low: configurationValue(valueArg, 1),
    energy_consumption_price_high: configurationValue(valueArg, 2),
    energy_production_price_low: configurationValue(valueArg, 3),
    energy_production_price_high: configurationValue(valueArg, 4),
  };
};

const watermeterFromApi = (valueArg: unknown): Record<string, unknown> | undefined => {
  if (!valueArg || Array.isArray(valueArg) && valueArg.length === 0) {
    return undefined;
  }
  const data = firstRecord(valueArg);
  return {
    consumption_day: numberValue(data.WATERMETER_CONSUMPTION_LITER),
    consumption_total: numberValue(data.WATERMETER_CONSUMPTION_TOTAL_M3),
    pulse_count: numberValue(data.WATERMETER_PULS_COUNT),
  };
};

const statusValue = (valueArg: unknown, idArg: number): number | undefined => numberValue(searchArray(valueArg, 'STATUS_ID', idArg, 'STATUS'));

const configurationValue = (valueArg: unknown, idArg: number): number | undefined => numberValue(searchArray(valueArg, 'CONFIGURATION_ID', idArg, 'PARAMETER'));

const searchArray = (valueArg: unknown, idKeyArg: string, idArg: number, valueKeyArg: string): unknown => {
  const rows = Array.isArray(valueArg) ? valueArg : [];
  const row = rows.find((rowArg) => {
    const item = recordValue(rowArg);
    return numberValue(item[idKeyArg]) === idArg;
  });
  return recordValue(row)[valueKeyArg];
};

const firstRecord = (valueArg: unknown): Record<string, unknown> => {
  if (Array.isArray(valueArg)) {
    return recordValue(valueArg[0]);
  }
  return recordValue(valueArg);
};

const endpointFromParts = (hostArg: string, portArg: number): IP1MonitorEndpoint => {
  const portPart = portArg === 80 ? '' : `:${portArg}`;
  return { host: hostArg, port: portArg, baseUrl: `http://${hostArg}${portPart}` };
};

const wattsValue = (valueArg: number | undefined): number | undefined => valueArg === undefined ? undefined : Math.round(valueArg * 1000);

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

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);
