import { SimpleLocalClient, type ISimpleLocalSnapshot, type IServiceCallRequest, type IServiceCallResult } from '../../core/index.js';
import { PeblarMapper } from './peblar.mapper.js';
import type { IPeblarConfig, IPeblarRawApiData } from './peblar.types.js';
import { peblarDefaultPort, peblarDefaultTimeoutMs, peblarProfile } from './peblar.types.js';

interface IPeblarEndpoint {
  baseUrl: string;
  host: string;
  port?: number;
}

export class PeblarClient extends SimpleLocalClient<IPeblarConfig> {
  private cachedSnapshot?: ISimpleLocalSnapshot;
  private cookies = new Map<string, string>();
  private loggedIn = false;
  private restApiToken?: string;
  private lastKnownChargingLimitAmps = 6;

  constructor(private readonly clientConfig: IPeblarConfig) {
    super(peblarProfile, clientConfig);
    this.restApiToken = stringValue(clientConfig.apiKey) || stringValue(clientConfig.token);
  }

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

    if (!forceRefreshArg && this.clientConfig.snapshot) {
      this.cachedSnapshot = PeblarMapper.toSnapshot({ config: this.clientConfig, source: 'snapshot', online: this.clientConfig.snapshot.online });
      return clone(this.cachedSnapshot);
    }

    if (this.endpoint()) {
      try {
        const rawData = await this.fetchRawData();
        this.rememberChargingLimit(rawData);
        this.cachedSnapshot = PeblarMapper.toSnapshot({ config: this.clientConfig, rawData, online: true, source: 'http' });
      } catch (errorArg) {
        this.cachedSnapshot = this.createOfflineSnapshot(errorMessage(errorArg));
      }
      return clone(this.cachedSnapshot);
    }

    this.cachedSnapshot = await super.getSnapshot(forceRefreshArg);
    return clone(this.cachedSnapshot);
  }

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

  public async execute(requestArg: IServiceCallRequest): Promise<IServiceCallResult> {
    if (this.clientConfig.client?.execute || this.clientConfig.commandExecutor) {
      return super.execute(requestArg);
    }
    if (!this.endpoint()) {
      return super.execute(requestArg);
    }

    try {
      const data = requestArg.data || {};
      if (requestArg.service === 'set_value') {
        const value = numberValue(data.value ?? data.native_value ?? data.current ?? data.charge_current_limit);
        if (value === undefined) {
          throw new Error('Peblar set_value requires data.value.');
        }
        const milliampere = value <= 100 ? Math.round(value * 1000) : Math.round(value);
        await this.updateEvInterface({ ChargeCurrentLimit: milliampere });
        if (milliampere >= 6000) {
          this.lastKnownChargingLimitAmps = Math.round(milliampere / 1000);
        }
        return { success: true, data: { ChargeCurrentLimit: milliampere } };
      }

      if (requestArg.service === 'turn_on' || requestArg.service === 'turn_off' || requestArg.service === 'toggle') {
        return await this.executeSwitch(requestArg, data);
      }

      if (requestArg.service === 'select_option') {
        const option = stringValue(data.option) || stringValue(data.smart_charging);
        if (!option) {
          throw new Error('Peblar select_option requires data.option.');
        }
        await this.setSmartCharging(option);
        return { success: true, data: { option } };
      }

      if (requestArg.service === 'press') {
        const button = stringValue(data.button) || stringValue(data.command) || entityTarget(requestArg, data) || 'identify';
        await this.pressButton(button);
        return { success: true, data: { button } };
      }

      return { success: false, error: `Unsupported Peblar service: ${requestArg.service}` };
    } catch (errorArg) {
      return { success: false, error: errorMessage(errorArg) };
    } finally {
      this.cachedSnapshot = undefined;
    }
  }

  public async destroy(): Promise<void> {
    await this.clientConfig.client?.destroy?.();
  }

  private async executeSwitch(requestArg: IServiceCallRequest, dataArg: Record<string, unknown>): Promise<IServiceCallResult> {
    const target = entityTarget(requestArg, dataArg);
    if (target?.includes('force_single_phase')) {
      let state = requestArg.service === 'turn_on';
      if (requestArg.service === 'toggle') {
        const snapshot = await this.getSnapshot(true);
        state = !Boolean(snapshot.entities.find((entityArg) => entityArg.id === 'force_single_phase')?.state);
      }
      await this.updateEvInterface({ Force1Phase: state });
      return { success: true, data: { Force1Phase: state } };
    }

    let on = requestArg.service === 'turn_on';
    if (requestArg.service === 'toggle') {
      const snapshot = await this.getSnapshot(true);
      on = !Boolean(snapshot.entities.find((entityArg) => entityArg.id === 'charge')?.state);
    }
    const value = on ? (numberValue(dataArg.value) || this.lastKnownChargingLimitAmps) * 1000 : 0;
    await this.updateEvInterface({ ChargeCurrentLimit: Math.round(value) });
    return { success: true, data: { ChargeCurrentLimit: Math.round(value) } };
  }

  private async fetchRawData(): Promise<IPeblarRawApiData> {
    const rawData: IPeblarRawApiData = { fetchedAt: new Date().toISOString() };

    if (this.clientConfig.password) {
      await this.login();
      rawData.systemInformation = await this.requestMainJson('system/info');
      rawData.userConfiguration = await this.requestMainJson('config/user');
      await this.ensureLocalRestApi(rawData.userConfiguration);
      this.restApiToken = await this.readApiToken();
      rawData.currentVersions = await this.optionalMainJson('system/software/automatic-update/current-versions');
      rawData.availableVersions = await this.optionalMainJson('system/software/automatic-update/available-versions');
    }

    if (!this.restApiToken) {
      throw new Error('Peblar live polling requires config.password or config.apiKey/config.token for the local REST API.');
    }

    rawData.ev = await this.requestRestJson('evinterface');
    rawData.meter = await this.requestRestJson('meter');
    rawData.system = await this.requestRestJson('system');
    return rawData;
  }

  private async ensureLocalRestApi(userConfigurationArg: Record<string, unknown> | undefined): Promise<void> {
    if (!userConfigurationArg) {
      return;
    }
    if (booleanValue(readField(userConfigurationArg, 'LocalRestApiAllowed', 'local_rest_api_allowed')) === false) {
      throw new Error('Peblar firmware reports LocalRestApiAllowed=false.');
    }
    const shouldEnable = this.clientConfig.enableRestApi ?? true;
    const desiredAccessMode = this.clientConfig.restApiAccessMode || 'ReadWrite';
    const enabled = booleanValue(readField(userConfigurationArg, 'LocalRestApiEnable', 'local_rest_api_enabled'));
    const accessMode = stringValue(readField(userConfigurationArg, 'LocalRestApiAccessMode', 'local_rest_api_access_mode'));
    const patch: Record<string, unknown> = {};
    if (shouldEnable && enabled !== true) {
      patch.LocalRestApiEnable = true;
    }
    if (accessMode && accessMode !== desiredAccessMode) {
      patch.LocalRestApiAccessMode = desiredAccessMode;
    }
    if (Object.keys(patch).length) {
      await this.requestMainJson('config/user', { method: 'PATCH', body: patch });
    }
  }

  private async readApiToken(): Promise<string> {
    const response = await this.requestMainJson('config/api-token');
    const token = stringValue(readField(response, 'ApiToken', 'api_token'));
    if (!token) {
      throw new Error('Peblar config/api-token response did not include ApiToken.');
    }
    return token;
  }

  private async updateEvInterface(bodyArg: Record<string, unknown>): Promise<Record<string, unknown>> {
    await this.ensureRestApiToken();
    return this.requestRestJson('evinterface', { method: 'PATCH', body: bodyArg });
  }

  private async setSmartCharging(optionArg: string): Promise<void> {
    await this.login();
    const patch = smartChargingPatch(optionArg);
    await this.requestMainJson('config/user', { method: 'PATCH', body: patch });
  }

  private async pressButton(buttonArg: string): Promise<void> {
    await this.login();
    const normalized = buttonArg.toLowerCase().replace(/[^a-z0-9]+/g, '_');
    if (normalized === 'identify') {
      await this.requestMainJson('system/identify', { method: 'PUT' });
      return;
    }
    if (normalized === 'reboot' || normalized === 'restart') {
      await this.requestMainJson('system/reboot', { method: 'POST', body: { RebootType: 'HardReboot' } });
      return;
    }
    if (normalized === 'socket_unlock' || normalized === 'unlock') {
      await this.requestMainJson('system/socket-unlock', { method: 'POST' });
      return;
    }
    throw new Error(`Unsupported Peblar button: ${buttonArg}`);
  }

  private async ensureRestApiToken(): Promise<void> {
    if (this.restApiToken) {
      return;
    }
    if (!this.clientConfig.password) {
      throw new Error('Peblar local REST commands require config.password or config.apiKey/config.token.');
    }
    await this.login();
    const userConfiguration = await this.requestMainJson('config/user');
    await this.ensureLocalRestApi(userConfiguration);
    this.restApiToken = await this.readApiToken();
  }

  private async login(): Promise<void> {
    if (this.loggedIn) {
      return;
    }
    if (!this.clientConfig.password) {
      throw new Error('Peblar main API commands require config.password.');
    }
    await this.requestMainJson('auth/login', { method: 'POST', body: { Password: this.clientConfig.password, PersistentSession: false }, allowUnauthenticated: true });
    this.loggedIn = true;
  }

  private async optionalMainJson(pathArg: string): Promise<Record<string, unknown> | undefined> {
    try {
      return await this.requestMainJson(pathArg);
    } catch {
      return undefined;
    }
  }

  private async requestMainJson(pathArg: string, optionsArg: { method?: 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE'; body?: unknown; allowUnauthenticated?: boolean } = {}): Promise<Record<string, unknown>> {
    return this.requestJson(this.url('api/v1', pathArg), {
      method: optionsArg.method || 'GET',
      body: optionsArg.body,
      headers: { 'Content-Type': 'application/json', ...(!optionsArg.allowUnauthenticated ? this.cookieHeader() : {}) },
      updateCookies: true,
    });
  }

  private async requestRestJson(pathArg: string, optionsArg: { method?: 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE'; body?: unknown; refreshed?: boolean } = {}): Promise<Record<string, unknown>> {
    await this.ensureRestApiToken();
    try {
      return await this.requestJson(this.url('api/wlac/v1', pathArg), {
        method: optionsArg.method || 'GET',
        body: optionsArg.body,
        headers: { 'Content-Type': 'application/json', Authorization: this.restApiToken || '' },
      });
    } catch (errorArg) {
      if (!optionsArg.refreshed && this.clientConfig.password && errorMessage(errorArg).includes('401')) {
        this.restApiToken = undefined;
        this.loggedIn = false;
        await this.ensureRestApiToken();
        return this.requestRestJson(pathArg, { ...optionsArg, refreshed: true });
      }
      throw errorArg;
    }
  }

  private async requestJson(urlArg: string, optionsArg: { method: string; body?: unknown; headers: Record<string, string>; updateCookies?: boolean }): Promise<Record<string, unknown>> {
    const timeoutMs = this.clientConfig.timeoutMs || peblarDefaultTimeoutMs;
    const controller = new AbortController();
    const timer = setTimeout(() => controller.abort(), timeoutMs);
    try {
      const response = await globalThis.fetch(urlArg, {
        method: optionsArg.method,
        headers: optionsArg.headers,
        body: optionsArg.body === undefined ? undefined : JSON.stringify(optionsArg.body),
        signal: controller.signal,
      });
      if (optionsArg.updateCookies) {
        this.updateCookies(response.headers);
      }
      const text = await response.text();
      if (response.status === 401) {
        throw new Error(`Peblar request ${new URL(urlArg).pathname} failed with 401 authentication error.`);
      }
      if (!response.ok) {
        throw new Error(`Peblar request ${new URL(urlArg).pathname} failed with ${response.status}: ${text.slice(0, 160)}`);
      }
      return text.trim() ? JSON.parse(text) as Record<string, unknown> : {};
    } finally {
      clearTimeout(timer);
    }
  }

  private updateCookies(headersArg: Headers): void {
    const headerValues = getSetCookie(headersArg);
    for (const header of headerValues) {
      const cookie = header.split(';')[0];
      const separator = cookie.indexOf('=');
      if (separator > 0) {
        this.cookies.set(cookie.slice(0, separator), cookie.slice(separator + 1));
      }
    }
  }

  private cookieHeader(): Record<string, string> {
    if (!this.cookies.size) {
      return {};
    }
    return { Cookie: Array.from(this.cookies.entries()).map(([key, value]) => `${key}=${value}`).join('; ') };
  }

  private rememberChargingLimit(rawDataArg: IPeblarRawApiData): void {
    const chargeCurrentLimit = numberValue(readField(rawDataArg.ev, 'ChargeCurrentLimit', 'charge_current_limit'));
    if (chargeCurrentLimit !== undefined && chargeCurrentLimit >= 6000) {
      this.lastKnownChargingLimitAmps = Math.round(chargeCurrentLimit / 1000);
    }
  }

  private url(prefixArg: 'api/v1' | 'api/wlac/v1', pathArg: string): string {
    const endpoint = this.requireEndpoint();
    return new URL(`${prefixArg}/${pathArg.replace(/^\/+/, '')}`, `${endpoint.baseUrl}/`).toString();
  }

  private requireEndpoint(): IPeblarEndpoint {
    const endpoint = this.endpoint();
    if (!endpoint) {
      throw new Error('Peblar local API requires config.host or config.url.');
    }
    return endpoint;
  }

  private endpoint(): IPeblarEndpoint | undefined {
    const configuredUrl = stringValue(this.clientConfig.url);
    if (configuredUrl) {
      return normalizeEndpoint(configuredUrl, this.clientConfig.port);
    }
    const host = stringValue(this.clientConfig.host);
    if (!host) {
      return undefined;
    }
    return normalizeEndpoint(host, this.clientConfig.port || peblarDefaultPort);
  }

  private createOfflineSnapshot(errorArg: string): ISimpleLocalSnapshot {
    return PeblarMapper.toSnapshot({ config: this.clientConfig, online: false, source: 'runtime', error: errorArg });
  }
}

const normalizeEndpoint = (valueArg: string, portArg?: number): IPeblarEndpoint | undefined => {
  try {
    const parsed = new URL(valueArg.includes('://') ? valueArg : `http://${valueArg}`);
    if (portArg) {
      parsed.port = String(portArg);
    }
    parsed.pathname = '';
    parsed.search = '';
    parsed.hash = '';
    return { baseUrl: parsed.toString().replace(/\/$/, ''), host: parsed.hostname, port: parsed.port ? Number(parsed.port) : undefined };
  } catch {
    return undefined;
  }
};

const smartChargingPatch = (optionArg: string): Record<string, unknown> => {
  const normalized = optionArg.toLowerCase();
  if (normalized === 'default') {
    return { ScheduledChargingEnable: false, SolarChargingEnable: false };
  }
  if (normalized === 'scheduled') {
    return { ScheduledChargingEnable: true, SolarChargingEnable: false };
  }
  if (normalized === 'fast_solar') {
    return { ScheduledChargingEnable: false, SolarChargingEnable: true, SolarChargingMode: 'MaxSolar' };
  }
  if (normalized === 'smart_solar') {
    return { ScheduledChargingEnable: false, SolarChargingEnable: true, SolarChargingMode: 'OptimizedSolar' };
  }
  if (normalized === 'pure_solar') {
    return { ScheduledChargingEnable: false, SolarChargingEnable: true, SolarChargingMode: 'PureSolar' };
  }
  throw new Error(`Unsupported Peblar smart charging option: ${optionArg}`);
};

const entityTarget = (requestArg: IServiceCallRequest, dataArg: Record<string, unknown>): string | undefined => {
  return stringValue(dataArg.entity) || stringValue(dataArg.entity_id) || stringValue(dataArg.entityId) || requestArg.target.entityId || requestArg.target.deviceId;
};

const getSetCookie = (headersArg: Headers): string[] => {
  const headersWithGetSetCookie = headersArg as Headers & { getSetCookie?: () => string[] };
  const values = headersWithGetSetCookie.getSetCookie?.();
  if (values?.length) {
    return values;
  }
  const single = headersArg.get('set-cookie');
  return single ? [single] : [];
};

const readField = (objectArg: Record<string, unknown> | undefined, ...keysArg: string[]): unknown => {
  if (!objectArg) {
    return undefined;
  }
  for (const key of keysArg) {
    if (objectArg[key] !== undefined) {
      return objectArg[key];
    }
  }
  return undefined;
};

const stringValue = (valueArg: unknown): string | undefined => {
  return 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 booleanValue = (valueArg: unknown): boolean | undefined => {
  if (typeof valueArg === 'boolean') {
    return valueArg;
  }
  if (typeof valueArg === 'number') {
    return valueArg !== 0;
  }
  if (typeof valueArg === 'string') {
    if (valueArg === '1' || valueArg.toLowerCase() === 'true') {
      return true;
    }
    if (valueArg === '0' || valueArg.toLowerCase() === 'false') {
      return false;
    }
  }
  return undefined;
};

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