import { SimpleLocalClient, type ISimpleLocalEntitySnapshot, type IServiceCallRequest, type IServiceCallResult } from '../../core/index.js';
import { ImeonInverterMapper } from './imeon_inverter.mapper.js';
import type { IImeonInverterConfig, IImeonInverterRawData, IImeonInverterSnapshot, IImeonInverterStorage, TImeonInverterMode, TImeonInverterModeCode } from './imeon_inverter.types.js';
import { imeonInverterCodeByMode, imeonInverterDefaultName, imeonInverterModeByCode, imeonInverterProfile } from './imeon_inverter.types.js';

export class ImeonInverterClient extends SimpleLocalClient<IImeonInverterConfig> {
  private authCookie?: string;

  constructor(private readonly configArg: IImeonInverterConfig) {
    super(imeonInverterProfile, configArg);
  }

  public async getSnapshot(forceRefreshArg = false): Promise<IImeonInverterSnapshot> {
    if (this.shouldUseLiveApi()) {
      try {
        return await this.fetchLiveSnapshot();
      } catch (errorArg) {
        this.authCookie = undefined;
        return this.offlineImeonSnapshot(errorMessage(errorArg));
      }
    }

    if (this.hasEndpoint() && (!this.username() || !this.password()) && !this.hasOfflineSource()) {
      return this.offlineImeonSnapshot('Imeon Inverter live polling requires config.username and config.password for POST /login authentication.');
    }

    return await super.getSnapshot(forceRefreshArg) as IImeonInverterSnapshot;
  }

  public async execute(requestArg: IServiceCallRequest): Promise<IServiceCallResult> {
    if (requestArg.service !== 'select_option') {
      return super.execute(requestArg);
    }

    if (this.shouldUseLiveApi()) {
      try {
        const option = this.selectOption(requestArg);
        const mode = modeCode(option);
        const response = await this.setInverterMode(mode);
        return { success: true, data: { mode, option: imeonInverterModeByCode[mode], response } };
      } catch (errorArg) {
        this.authCookie = undefined;
        return { success: false, error: errorMessage(errorArg) };
      }
    }

    if (this.configArg.client?.execute || this.configArg.commandExecutor) {
      return super.execute(requestArg);
    }

    return {
      success: false,
      error: 'Imeon Inverter select_option requires config.host/config.url plus config.username and config.password, or an injected client.execute/commandExecutor.',
    };
  }

  private async fetchLiveSnapshot(): Promise<IImeonInverterSnapshot> {
    await this.login();
    const storage: IImeonInverterStorage = {};
    const [timedData, monitoring, monitoringMinute, manager, smartload, onetime] = await Promise.all([
      this.getTimedData(),
      this.getMonitoringData(this.configArg.monitoringTime || 'hour'),
      this.getMonitoringData('minute'),
      this.getResultObject('/api/manager'),
      this.getSmartloadData(),
      this.getOnetimeData(),
    ]);

    Object.assign(storage, timedData);
    storage.monitoring = monitoring;
    storage.monitoring_minute = monitoringMinute;
    storage.manager = manager;
    storage.smartload = smartload;
    storage.inverter = onetime;

    const flattened = flattenStorage(storage);
    const rawData: IImeonInverterRawData = { storage, flattened };
    const endpoint = this.endpoint();
    const inverter = objectValue(storage.inverter);
    const serial = stringValue(inverter.serial) || stringValue(this.configArg.uniqueId);
    return ImeonInverterMapper.toSnapshot({
      config: this.configArg,
      rawData: {
        device: {
          id: this.configArg.uniqueId || serial || endpoint?.host || imeonInverterProfile.domain,
          name: this.configArg.name || (serial ? `Imeon ${serial}` : imeonInverterDefaultName),
          manufacturer: 'Imeon Energy',
          model: stringValue(inverter.inverter) || stringValue(this.configArg.model) || 'Imeon inverter',
          serialNumber: serial,
          host: endpoint?.host,
          port: endpoint?.port,
          protocol: 'http',
          configurationUrl: endpoint?.origin,
          attributes: {
            software: inverter.software,
          },
        },
        entities: entitiesFromFlattened(flattened),
        online: true,
        updatedAt: new Date().toISOString(),
        source: 'http',
        rawData,
        capabilities: {
          localRead: true,
          localControl: true,
          selectOption: true,
        },
      },
      online: true,
      source: 'http',
    }) as IImeonInverterSnapshot;
  }

  private async login(): Promise<void> {
    const username = this.username();
    const password = this.password();
    if (!username || !password) {
      throw new Error('Imeon Inverter login requires config.username and config.password.');
    }

    const body = new URLSearchParams();
    body.set('do_login', 'true');
    body.set('email', username);
    body.set('passwd', password);
    const response = await this.request('/login', { method: 'POST', body });
    const text = await response.text();
    if (!response.ok) {
      throw new Error(`Imeon Inverter login failed with HTTP ${response.status}: ${text.slice(0, 200)}`);
    }
    this.storeCookies(response);
    const json = parseJsonObject(text);
    if (json && json.accessGranted === false) {
      throw new Error('Imeon Inverter login failed: accessGranted is false.');
    }
  }

  private async getTimedData(): Promise<IImeonInverterStorage> {
    const time = this.configArg.timedDataTime || 'minute';
    const endpoints: Array<[keyof IImeonInverterStorage, string, Record<string, string>?]> = [
      ['battery', '/api/battery'],
      ['grid', '/api/grid', { threephase: 'true' }],
      ['pv', '/api/pv'],
      ['input', '/api/input'],
      ['output', '/api/output', { threephase: 'true' }],
      ['meter', '/api/em'],
      ['temp', '/api/temp'],
      ['timeline', '/api/timeline'],
      ['energy', '/api/energy'],
      ['forecast', '/api/forecast'],
    ];
    const result: IImeonInverterStorage = {};
    for (const [key, path, params] of endpoints) {
      result[key] = await this.getResultObject(path, { ...params, time });
    }
    return result;
  }

  private async getMonitoringData(timeArg: string): Promise<Record<string, unknown>> {
    return this.getResultObject('/api/monitor', { time: timeArg });
  }

  private async getSmartloadData(): Promise<Record<string, unknown>> {
    const data = await this.requestJson('/api/smartload');
    if (typeof data.result === 'string') {
      return { ...data, result: parseMaybeJson(data.result) };
    }
    return data;
  }

  private async getOnetimeData(): Promise<Record<string, unknown>> {
    const data = await this.requestJson('/data');
    const enableStatus = objectValue(data.enable_status);
    return {
      inverter: data.type,
      software: data.software,
      serial: data.serial,
      charging_current_limit: data.max_ac_charging_current,
      injection_power_limit: data.injection_power,
      battery_night_discharge: enableStatus.discharge_night === '1',
      battery_grid_charge: enableStatus.charge_bat_with_grid === '1',
    };
  }

  private async getResultObject(pathArg: string, paramsArg?: Record<string, string>): Promise<Record<string, unknown>> {
    const data = await this.requestJson(pathArg, { params: paramsArg });
    const result = typeof data.result === 'string' ? parseMaybeJson(data.result) : data.result;
    return objectValue(result);
  }

  private async setInverterMode(modeArg: TImeonInverterModeCode): Promise<string> {
    await this.login();
    const body = new FormData();
    body.set('inverter_mode', modeArg);
    body.set('permasave', String(Boolean(this.configArg.performSave)));
    const response = await this.request('/api/set', { method: 'POST', body });
    const text = await response.text();
    if (!response.ok) {
      throw new Error(`Imeon Inverter set inverter mode failed with HTTP ${response.status}: ${text.slice(0, 200)}`);
    }
    this.storeCookies(response);
    return text;
  }

  private async requestJson(pathArg: string, optionsArg: { params?: Record<string, string> } = {}): Promise<Record<string, unknown>> {
    const response = await this.request(pathArg, { method: 'GET', params: optionsArg.params });
    const text = await response.text();
    if (!response.ok) {
      throw new Error(`Imeon Inverter GET ${pathArg} failed with HTTP ${response.status}: ${text.slice(0, 200)}`);
    }
    this.storeCookies(response);
    return parseJsonObject(text) || {};
  }

  private async request(pathArg: string, optionsArg: { method: 'GET' | 'POST'; params?: Record<string, string>; body?: BodyInit }): Promise<Response> {
    const endpoint = this.endpoint();
    if (!endpoint) {
      throw new Error('Imeon Inverter live HTTP requests require config.host or config.url.');
    }

    const url = new URL(`${endpoint.origin}${pathArg.startsWith('/') ? pathArg : `/${pathArg}`}`);
    for (const [key, value] of Object.entries(optionsArg.params || {})) {
      url.searchParams.set(key, value);
    }
    const headers: Record<string, string> = { accept: 'application/json, text/plain;q=0.9, */*;q=0.8' };
    if (this.authCookie) {
      headers.cookie = this.authCookie;
    }
    if (optionsArg.body instanceof URLSearchParams) {
      headers['content-type'] = 'application/x-www-form-urlencoded;charset=UTF-8';
    }

    const controller = new AbortController();
    const timer = setTimeout(() => controller.abort(), this.timeoutMs());
    try {
      return await globalThis.fetch(url, {
        method: optionsArg.method,
        headers,
        body: optionsArg.body,
        signal: controller.signal,
      });
    } finally {
      clearTimeout(timer);
    }
  }

  private storeCookies(responseArg: Response): void {
    const setCookie = responseArg.headers.get('set-cookie');
    if (!setCookie) {
      return;
    }
    this.authCookie = setCookie
      .split(/,(?=\s*[^;,]+=)/)
      .map((cookieArg) => cookieArg.trim().split(';')[0])
      .filter(Boolean)
      .join('; ');
  }

  private selectOption(requestArg: IServiceCallRequest): string {
    const option = stringValue(requestArg.data?.option) || stringValue(requestArg.data?.mode) || stringValue(requestArg.data?.value);
    if (!option) {
      throw new Error('Imeon Inverter select_option requires data.option, data.mode, or data.value.');
    }
    return option;
  }

  private shouldUseLiveApi(): boolean {
    return Boolean(this.endpoint() && this.username() && this.password() && !this.hasOfflineSource());
  }

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

  private hasEndpoint(): boolean {
    return Boolean(this.endpoint());
  }

  private endpoint(): { origin: string; host: string; port?: number } | undefined {
    const rawUrl = stringValue(this.configArg.url) || (stringValue(this.configArg.host)?.includes('://') ? stringValue(this.configArg.host) : undefined);
    if (rawUrl) {
      const url = new URL(rawUrl);
      return {
        origin: `${url.protocol}//${url.host}`,
        host: url.hostname,
        port: url.port ? Number(url.port) : undefined,
      };
    }
    const host = stringValue(this.configArg.host);
    if (!host) {
      return undefined;
    }
    const port = this.configArg.port || imeonInverterProfile.defaultPort;
    const portPart = port && port !== 80 ? `:${port}` : '';
    return { origin: `http://${host}${portPart}`, host, port };
  }

  private username(): string | undefined {
    return stringValue(this.configArg.username);
  }

  private password(): string | undefined {
    return stringValue(this.configArg.password);
  }

  private timeoutMs(): number {
    return typeof this.configArg.timeoutMs === 'number' && this.configArg.timeoutMs > 0 ? this.configArg.timeoutMs : 15000;
  }

  private offlineImeonSnapshot(errorArg: string): IImeonInverterSnapshot {
    const endpoint = this.endpoint();
    return ImeonInverterMapper.toSnapshot({
      config: this.configArg,
      rawData: {
        device: {
          id: this.configArg.uniqueId || endpoint?.host || imeonInverterProfile.domain,
          name: this.configArg.name || imeonInverterDefaultName,
          manufacturer: 'Imeon Energy',
          model: 'Imeon inverter',
          host: endpoint?.host,
          port: endpoint?.port,
          protocol: 'http',
          configurationUrl: endpoint?.origin,
        },
        entities: [{ id: 'online', name: 'Online', platform: 'binary_sensor', state: false, available: true, attributes: { error: errorArg } }],
        online: false,
        updatedAt: new Date().toISOString(),
        source: 'runtime',
        error: errorArg,
      },
      online: false,
      source: 'runtime',
      error: errorArg,
    }) as IImeonInverterSnapshot;
  }
}

const imeonSensorKeys = new Set([
  'battery_power',
  'battery_soc',
  'battery_status',
  'battery_stored',
  'battery_consumed',
  'grid_current_l1',
  'grid_current_l2',
  'grid_current_l3',
  'grid_frequency',
  'grid_voltage_l1',
  'grid_voltage_l2',
  'grid_voltage_l3',
  'input_power_l1',
  'input_power_l2',
  'input_power_l3',
  'input_power_total',
  'inverter_charging_current_limit',
  'inverter_injection_power_limit',
  'manager_inverter_state',
  'meter_power',
  'output_current_l1',
  'output_current_l2',
  'output_current_l3',
  'output_frequency',
  'output_power_l1',
  'output_power_l2',
  'output_power_l3',
  'output_power_total',
  'output_voltage_l1',
  'output_voltage_l2',
  'output_voltage_l3',
  'pv_consumed',
  'pv_injected',
  'pv_power_1',
  'pv_power_2',
  'pv_power_total',
  'temp_air_temperature',
  'temp_component_temperature',
  'monitoring_self_consumption',
  'monitoring_self_sufficiency',
  'monitoring_minute_building_consumption',
  'monitoring_minute_grid_consumption',
  'monitoring_minute_grid_injection',
  'monitoring_minute_grid_power_flow',
  'monitoring_minute_solar_production',
  'timeline_type_msg',
  'energy_pv',
  'energy_grid_injected',
  'energy_grid_consumed',
  'energy_building_consumption',
  'energy_battery_stored',
  'energy_battery_consumed',
  'forecast_cons_remaining_today',
  'forecast_prod_remaining_today',
]);

const entitiesFromFlattened = (flattenedArg: Record<string, string | number | boolean | null>): ISimpleLocalEntitySnapshot[] => {
  const entities: ISimpleLocalEntitySnapshot[] = [];
  for (const [key, value] of Object.entries(flattenedArg)) {
    if (key === 'manager_inverter_mode') {
      const mode = typeof value === 'string' ? imeonInverterModeByCode[value as TImeonInverterModeCode] || value : value;
      entities.push({
        id: key,
        name: 'Manager inverter mode',
        platform: 'select',
        state: mode,
        writable: true,
        available: value !== undefined,
        attributes: {
          options: Object.values(imeonInverterModeByCode),
          rawMode: value,
        },
      });
      continue;
    }
    if (!imeonSensorKeys.has(key)) {
      continue;
    }
    entities.push({
      id: key,
      name: titleCase(key),
      platform: 'sensor',
      state: value,
      available: value !== undefined,
      writable: false,
      unit: unitForKey(key),
      deviceClass: deviceClassForKey(key),
      stateClass: stateClassForKey(key),
      attributes: enumOptionsForKey(key),
    });
  }
  return entities;
};

const flattenStorage = (storageArg: IImeonInverterStorage): Record<string, string | number | boolean | null> => {
  const flattened: Record<string, string | number | boolean | null> = {};
  for (const [section, value] of Object.entries(storageArg)) {
    if (!value || typeof value !== 'object' || Array.isArray(value)) {
      continue;
    }
    for (const [key, item] of Object.entries(value as Record<string, unknown>)) {
      if (isPrimitive(item)) {
        flattened[`${section}_${key}`] = item;
      }
    }
  }
  return flattened;
};

const modeCode = (valueArg: string): TImeonInverterModeCode => {
  if (valueArg in imeonInverterModeByCode) {
    return valueArg as TImeonInverterModeCode;
  }
  const code = imeonInverterCodeByMode[valueArg as TImeonInverterMode];
  if (code) {
    return code;
  }
  throw new Error(`Unsupported Imeon Inverter mode: ${valueArg}. Supported modes: ${Object.values(imeonInverterModeByCode).join(', ')}.`);
};

const parseJsonObject = (textArg: string): Record<string, unknown> | undefined => {
  const parsed = parseMaybeJson(textArg);
  return objectValue(parsed);
};

const parseMaybeJson = (valueArg: unknown): unknown => {
  if (typeof valueArg !== 'string') {
    return valueArg;
  }
  const trimmed = valueArg.trim();
  if (!trimmed) {
    return {};
  }
  try {
    return JSON.parse(trimmed) as unknown;
  } catch {
    return valueArg;
  }
};

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

const isPrimitive = (valueArg: unknown): valueArg is string | number | boolean | null => valueArg === null || ['string', 'number', 'boolean'].includes(typeof valueArg);

const unitForKey = (keyArg: string): string | undefined => {
  if (keyArg.includes('soc') || keyArg.includes('self_')) {
    return '%';
  }
  if (keyArg.includes('current')) {
    return 'A';
  }
  if (keyArg.includes('voltage')) {
    return 'V';
  }
  if (keyArg.includes('frequency')) {
    return 'Hz';
  }
  if (keyArg.includes('temperature')) {
    return '°C';
  }
  if (keyArg.startsWith('energy_') || keyArg.startsWith('forecast_')) {
    return 'Wh';
  }
  if (keyArg.includes('power') || keyArg.endsWith('_stored') || keyArg.endsWith('_consumed') || keyArg.endsWith('_injected')) {
    return 'W';
  }
  return undefined;
};

const deviceClassForKey = (keyArg: string): string | undefined => {
  if (keyArg.includes('soc')) {
    return 'battery';
  }
  if (keyArg.includes('current')) {
    return 'current';
  }
  if (keyArg.includes('voltage')) {
    return 'voltage';
  }
  if (keyArg.includes('frequency')) {
    return 'frequency';
  }
  if (keyArg.includes('temperature')) {
    return 'temperature';
  }
  if (keyArg.startsWith('energy_') || keyArg.startsWith('forecast_')) {
    return 'energy';
  }
  if (keyArg.includes('power') || keyArg.endsWith('_stored') || keyArg.endsWith('_consumed') || keyArg.endsWith('_injected')) {
    return 'power';
  }
  if (keyArg.endsWith('_status') || keyArg.endsWith('_state') || keyArg.endsWith('_type_msg')) {
    return 'enum';
  }
  return undefined;
};

const stateClassForKey = (keyArg: string): string | undefined => {
  if (keyArg.endsWith('_status') || keyArg.endsWith('_state') || keyArg.endsWith('_type_msg')) {
    return undefined;
  }
  if (keyArg.startsWith('energy_')) {
    return 'total_increasing';
  }
  if (keyArg.startsWith('monitoring_self_')) {
    return 'total';
  }
  return typeof keyArg === 'string' ? 'measurement' : undefined;
};

const enumOptionsForKey = (keyArg: string): Record<string, unknown> | undefined => {
  if (keyArg === 'battery_status') {
    return { options: ['charging', 'discharging', 'charged'] };
  }
  if (keyArg === 'manager_inverter_state') {
    return { options: ['not_connected', 'unsynchronized', 'grid_consumption', 'grid_injection', 'grid_synchronized_but_not_used'] };
  }
  if (keyArg === 'timeline_type_msg') {
    return { options: ['com_lost', 'warning_grid', 'warning_pv', 'warning_bat', 'error_ond', 'error_soft', 'error_pv', 'error_grid', 'error_bat', 'good_1', 'info_soft', 'info_ond', 'info_bat', 'info_smartlo'] };
  }
  return undefined;
};

const titleCase = (valueArg: string): string => valueArg.replace(/[_-]+/g, ' ').replace(/\b\w/g, (charArg) => charArg.toUpperCase());

const stringValue = (valueArg: unknown): string | undefined => typeof valueArg === 'string' && valueArg.trim() ? valueArg.trim() : undefined;

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