import * as plugins from '../../plugins.js';
import type { IIntegrationEntity, IServiceCallRequest } from '../../core/types.js';
import type {
  IAdguardClientCommand,
  IAdguardFilterSubscription,
  IAdguardQueryLogConfig,
  IAdguardSafeSearchConfig,
  IAdguardSnapshot,
  TAdguardSwitchKey,
} from './adguard.types.js';

const adguardDomain = 'adguard';

const switchDescriptions: Array<{ key: TAdguardSwitchKey; name: string }> = [
  { key: 'protection', name: 'Protection' },
  { key: 'parental', name: 'Parental control' },
  { key: 'safesearch', name: 'Safe search' },
  { key: 'safebrowsing', name: 'Safe browsing' },
  { key: 'filtering', name: 'Filtering' },
  { key: 'querylog', name: 'Query log' },
];

const sensorDescriptions: Array<{ key: string; name: string; unit?: string; value: (snapshotArg: IAdguardSnapshot) => number | string | null }> = [
  { key: 'dns_queries', name: 'DNS queries', unit: 'queries', value: (snapshotArg) => numberOrNull(snapshotArg.stats.num_dns_queries) ?? sum(snapshotArg.stats.dns_queries) },
  { key: 'blocked_filtering', name: 'DNS queries blocked', unit: 'queries', value: (snapshotArg) => numberOrNull(snapshotArg.stats.num_blocked_filtering) ?? sum(snapshotArg.stats.blocked_filtering) },
  { key: 'blocked_percentage', name: 'DNS queries blocked ratio', unit: '%', value: (snapshotArg) => blockedPercentage(snapshotArg) },
  { key: 'blocked_parental', name: 'Parental control blocked', unit: 'requests', value: (snapshotArg) => numberOrNull(snapshotArg.stats.num_replaced_parental) ?? sum(snapshotArg.stats.replaced_parental) },
  { key: 'blocked_safebrowsing', name: 'Safe browsing blocked', unit: 'requests', value: (snapshotArg) => numberOrNull(snapshotArg.stats.num_replaced_safebrowsing) ?? sum(snapshotArg.stats.replaced_safebrowsing) },
  { key: 'enforced_safesearch', name: 'Safe searches enforced', unit: 'requests', value: (snapshotArg) => numberOrNull(snapshotArg.stats.num_replaced_safesearch) ?? sum(snapshotArg.stats.replaced_safesearch) },
  { key: 'average_speed', name: 'Average processing speed', unit: 'ms', value: (snapshotArg) => rounded(numberOrNull(snapshotArg.stats.avg_processing_time) === null ? null : numberOrNull(snapshotArg.stats.avg_processing_time)! * 1000) },
  { key: 'rules_count', name: 'Rules count', unit: 'rules', value: (snapshotArg) => rulesCount(snapshotArg.filtering.filters) },
];

export class AdguardMapper {
  public static toDevices(snapshotArg: IAdguardSnapshot): plugins.shxInterfaces.data.IDeviceDefinition[] {
    const updatedAt = snapshotArg.updatedAt || new Date().toISOString();
    const deviceId = this.deviceId(snapshotArg);
    const features: plugins.shxInterfaces.data.IDeviceFeature[] = [
      { id: 'connectivity', capability: 'sensor', name: 'Connectivity', readable: true, writable: false },
      { id: 'running', capability: 'sensor', name: 'Running', readable: true, writable: false },
      { id: 'version', capability: 'sensor', name: 'Version', readable: true, writable: false },
    ];
    const state: plugins.shxInterfaces.data.IDeviceState[] = [
      { featureId: 'connectivity', value: snapshotArg.online ? 'online' : 'offline', updatedAt },
      { featureId: 'running', value: snapshotArg.status.running ?? snapshotArg.online, updatedAt },
      { featureId: 'version', value: snapshotArg.status.version || null, updatedAt },
    ];

    for (const description of switchDescriptions) {
      features.push({ id: description.key, capability: 'switch', name: description.name, readable: true, writable: true });
      state.push({ featureId: description.key, value: this.switchState(snapshotArg, description.key), updatedAt });
    }

    for (const sensor of sensorDescriptions) {
      features.push({ id: sensor.key, capability: 'sensor', name: sensor.name, readable: true, writable: false, unit: sensor.unit });
      state.push({ featureId: sensor.key, value: sensor.value(snapshotArg), updatedAt });
    }

    return [{
      id: deviceId,
      integrationDomain: adguardDomain,
      name: this.deviceName(snapshotArg),
      protocol: 'http',
      manufacturer: 'AdGuard Team',
      model: 'AdGuard Home',
      online: snapshotArg.online,
      features,
      state,
      metadata: this.cleanAttributes({
        host: snapshotArg.host,
        port: snapshotArg.port,
        basePath: snapshotArg.basePath,
        version: snapshotArg.status.version,
        language: snapshotArg.status.language,
        dnsPort: snapshotArg.status.dns_port,
        httpPort: snapshotArg.status.http_port,
        source: snapshotArg.source,
        error: snapshotArg.error,
      }),
    }];
  }

  public static toEntities(snapshotArg: IAdguardSnapshot): IIntegrationEntity[] {
    const deviceId = this.deviceId(snapshotArg);
    const baseName = this.deviceName(snapshotArg);
    const baseSlug = this.slug(baseName);
    const uniqueBase = this.uniqueBase(snapshotArg);
    const entities: IIntegrationEntity[] = [
      this.entity('binary_sensor', `${baseName} Running`, deviceId, `${uniqueBase}_running`, snapshotArg.status.running ?? snapshotArg.online ? 'on' : 'off', snapshotArg.online, {
        deviceClass: 'running',
        host: snapshotArg.host,
        port: snapshotArg.port,
      }),
    ];

    if (snapshotArg.status.version) {
      entities.push(this.entity('sensor', `${baseName} Version`, deviceId, `${uniqueBase}_version`, snapshotArg.status.version, snapshotArg.online, {
        entityCategory: 'diagnostic',
      }));
    }

    for (const description of switchDescriptions) {
      const state = this.switchState(snapshotArg, description.key);
      entities.push({
        id: `switch.${baseSlug}_${this.slug(description.name)}`,
        uniqueId: `adguard_${uniqueBase}_switch_${description.key}`,
        integrationDomain: adguardDomain,
        deviceId,
        platform: 'switch',
        name: `${baseName} ${description.name}`,
        state: state ? 'on' : 'off',
        attributes: this.cleanAttributes({
          adguardSwitchKey: description.key,
          writable: true,
        }),
        available: snapshotArg.online,
      });
    }

    for (const sensor of sensorDescriptions) {
      const value = sensor.value(snapshotArg);
      entities.push(this.entity('sensor', `${baseName} ${sensor.name}`, deviceId, `${uniqueBase}_sensor_${sensor.key}`, value, snapshotArg.online && value !== null, {
        unit: sensor.unit,
        stateClass: typeof value === 'number' ? 'measurement' : undefined,
        entityRegistryEnabledDefault: sensor.key === 'rules_count' ? false : undefined,
      }));
    }

    if (snapshotArg.update && !snapshotArg.update.disabled) {
      entities.push(this.entity('update', baseName, deviceId, `${uniqueBase}_update`, snapshotArg.update.new_version && snapshotArg.update.new_version !== snapshotArg.status.version ? 'on' : 'off', snapshotArg.online, {
        installedVersion: snapshotArg.status.version,
        latestVersion: snapshotArg.update.new_version,
        releaseSummary: snapshotArg.update.announcement,
        releaseUrl: snapshotArg.update.announcement_url,
        canAutoupdate: snapshotArg.update.can_autoupdate,
      }));
    }

    return entities;
  }

  public static commandForService(snapshotArg: IAdguardSnapshot, requestArg: IServiceCallRequest): IAdguardClientCommand | { error: string } | undefined {
    if (requestArg.domain === adguardDomain) {
      return this.adguardServiceCommand(snapshotArg, requestArg);
    }

    if (requestArg.domain === 'switch' && ['turn_on', 'turn_off'].includes(requestArg.service)) {
      const target = this.targetSwitch(snapshotArg, requestArg);
      if ('error' in target) {
        return target;
      }
      return this.switchCommand(snapshotArg, requestArg.service, target.key, requestArg, target.entity);
    }

    if (requestArg.domain === 'update' && requestArg.service === 'install') {
      const entity = this.findTargetEntity(snapshotArg, requestArg);
      if (!entity || entity.platform !== 'update') {
        return { error: 'AdGuard update.install requires the AdGuard update entity target.' };
      }
      return {
        type: 'begin_update',
        service: 'install',
        method: 'POST',
        path: '/update',
        target: requestArg.target,
        entityId: entity.id,
        deviceId: entity.deviceId,
        uniqueId: entity.uniqueId,
      };
    }

    return undefined;
  }

  public static deviceId(snapshotArg: IAdguardSnapshot): string {
    return `adguard.service.${this.uniqueBase(snapshotArg)}`;
  }

  public static switchState(snapshotArg: IAdguardSnapshot, keyArg: TAdguardSwitchKey): boolean {
    if (keyArg === 'protection') return Boolean(snapshotArg.status.protection_enabled);
    if (keyArg === 'filtering') return Boolean(snapshotArg.filtering.enabled);
    if (keyArg === 'querylog') return Boolean(snapshotArg.queryLog.enabled);
    if (keyArg === 'safebrowsing') return this.booleanStatus(snapshotArg.safebrowsing);
    if (keyArg === 'safesearch') return Boolean(snapshotArg.safesearch.enabled);
    return this.booleanStatus(snapshotArg.parental);
  }

  public static slug(valueArg: string): string {
    return valueArg.toLowerCase().replace(/[^a-z0-9]+/g, '_').replace(/^_+|_+$/g, '') || 'adguard';
  }

  private static adguardServiceCommand(snapshotArg: IAdguardSnapshot, requestArg: IServiceCallRequest): IAdguardClientCommand | { error: string } | undefined {
    if (requestArg.service === 'add_url') {
      const name = stringValue(requestArg.data?.name);
      const url = stringValue(requestArg.data?.url);
      if (!name || !url || !isUrlOrAbsolutePath(url)) {
        return { error: 'AdGuard add_url requires data.name and data.url as a URL or absolute path.' };
      }
      return this.command('add_filter_url', 'add_url', 'POST', '/filtering/add_url', requestArg, { name, url, whitelist: false }, { name, url });
    }

    if (requestArg.service === 'remove_url') {
      const url = stringValue(requestArg.data?.url);
      if (!url || !isUrlOrAbsolutePath(url)) {
        return { error: 'AdGuard remove_url requires data.url as a URL or absolute path.' };
      }
      return this.command('remove_filter_url', 'remove_url', 'POST', '/filtering/remove_url', requestArg, { url, whitelist: false }, { url });
    }

    if (requestArg.service === 'enable_url' || requestArg.service === 'disable_url') {
      const url = stringValue(requestArg.data?.url);
      if (!url || !isUrlOrAbsolutePath(url)) {
        return { error: 'AdGuard enable_url/disable_url requires data.url as a URL or absolute path.' };
      }
      const filter = this.filterByUrl(snapshotArg, url);
      if (!filter) {
        return { error: `AdGuard filter URL is not present in the current filtering snapshot: ${url}` };
      }
      const enabled = requestArg.service === 'enable_url';
      return this.command('set_filter_url_enabled', requestArg.service, 'POST', '/filtering/set_url', requestArg, {
        url,
        whitelist: false,
        data: {
          name: filter.name || url,
          url,
          enabled,
        },
      }, { url, name: filter.name || url, enabled });
    }

    if (requestArg.service === 'refresh') {
      const force = booleanValue(requestArg.data?.force) ?? false;
      return this.command('refresh_filters', 'refresh', 'POST', '/filtering/refresh', requestArg, { whitelist: false, force }, { force });
    }

    return undefined;
  }

  private static switchCommand(snapshotArg: IAdguardSnapshot, serviceArg: string, keyArg: TAdguardSwitchKey, requestArg: IServiceCallRequest, entityArg: IIntegrationEntity | undefined): IAdguardClientCommand {
    const enabled = serviceArg === 'turn_on';
    if (keyArg === 'protection') {
      return this.command('set_protection', serviceArg, 'POST', '/protection', requestArg, { enabled }, { enabled, switchKey: keyArg, entity: entityArg });
    }
    if (keyArg === 'filtering') {
      return this.command('set_filtering', serviceArg, 'POST', '/filtering/config', requestArg, { enabled }, { enabled, switchKey: keyArg, entity: entityArg });
    }
    if (keyArg === 'querylog') {
      return this.command('set_querylog', serviceArg, 'PUT', '/querylog/config/update', requestArg, this.queryLogPayload(snapshotArg.queryLog, enabled), { enabled, switchKey: keyArg, entity: entityArg });
    }
    if (keyArg === 'safebrowsing') {
      return this.command('set_safebrowsing', serviceArg, 'POST', `/safebrowsing/${enabled ? 'enable' : 'disable'}`, requestArg, undefined, { enabled, switchKey: keyArg, entity: entityArg });
    }
    if (keyArg === 'safesearch') {
      return this.command('set_safesearch', serviceArg, 'PUT', '/safesearch/settings', requestArg, this.safeSearchPayload(snapshotArg.safesearch, enabled), { enabled, switchKey: keyArg, entity: entityArg });
    }
    return this.command('set_parental', serviceArg, 'POST', `/parental/${enabled ? 'enable' : 'disable'}`, requestArg, undefined, { enabled, switchKey: keyArg, entity: entityArg });
  }

  private static targetSwitch(snapshotArg: IAdguardSnapshot, requestArg: IServiceCallRequest): { key: TAdguardSwitchKey; entity?: IIntegrationEntity } | { error: string } {
    const entity = this.findTargetEntity(snapshotArg, requestArg);
    const key = entity?.attributes?.adguardSwitchKey;
    if (isSwitchKey(key)) {
      return { key, entity };
    }
    if (requestArg.target.deviceId && requestArg.target.deviceId === this.deviceId(snapshotArg)) {
      return { error: 'AdGuard switch service calls require a switch entity target because the AdGuard device has multiple switches.' };
    }
    return { error: 'AdGuard switch service calls require an AdGuard switch entity target.' };
  }

  private static command(typeArg: IAdguardClientCommand['type'], serviceArg: string, methodArg: IAdguardClientCommand['method'], pathArg: string, requestArg: IServiceCallRequest, payloadArg?: Record<string, unknown>, optionsArg: { entity?: IIntegrationEntity; enabled?: boolean; switchKey?: TAdguardSwitchKey; url?: string; name?: string; force?: boolean } = {}): IAdguardClientCommand {
    return this.cleanAttributes({
      type: typeArg,
      service: serviceArg,
      method: methodArg,
      path: pathArg,
      payload: payloadArg,
      target: requestArg.target,
      entityId: optionsArg.entity?.id || requestArg.target.entityId,
      deviceId: optionsArg.entity?.deviceId || requestArg.target.deviceId,
      uniqueId: optionsArg.entity?.uniqueId,
      switchKey: optionsArg.switchKey,
      enabled: optionsArg.enabled,
      url: optionsArg.url,
      name: optionsArg.name,
      force: optionsArg.force,
    }) as IAdguardClientCommand;
  }

  private static findTargetEntity(snapshotArg: IAdguardSnapshot, requestArg: IServiceCallRequest): IIntegrationEntity | undefined {
    if (!requestArg.target.entityId) {
      return undefined;
    }
    return this.toEntities(snapshotArg).find((entityArg) => entityArg.id === requestArg.target.entityId);
  }

  private static filterByUrl(snapshotArg: IAdguardSnapshot, urlArg: string): IAdguardFilterSubscription | undefined {
    return (snapshotArg.filtering.filters || []).find((filterArg) => filterArg.url === urlArg);
  }

  private static queryLogPayload(configArg: IAdguardQueryLogConfig, enabledArg: boolean): Record<string, unknown> {
    return {
      enabled: enabledArg,
      interval: configArg.interval ?? 7776000000,
      anonymize_client_ip: configArg.anonymize_client_ip ?? false,
      ignored: configArg.ignored || [],
      ignored_enabled: configArg.ignored_enabled ?? false,
    };
  }

  private static safeSearchPayload(configArg: IAdguardSafeSearchConfig, enabledArg: boolean): Record<string, unknown> {
    return {
      ...configArg,
      enabled: enabledArg,
    };
  }

  private static entity(platformArg: IIntegrationEntity['platform'], nameArg: string, deviceIdArg: string, uniqueIdArg: string, stateArg: unknown, availableArg: boolean, attributesArg: Record<string, unknown> = {}): IIntegrationEntity {
    return {
      id: `${platformArg}.${this.slug(nameArg)}`,
      uniqueId: `adguard_${uniqueIdArg}`,
      integrationDomain: adguardDomain,
      deviceId: deviceIdArg,
      platform: platformArg,
      name: nameArg,
      state: stateArg,
      attributes: this.cleanAttributes(attributesArg),
      available: availableArg,
    };
  }

  private static deviceName(snapshotArg: IAdguardSnapshot): string {
    return snapshotArg.name || 'AdGuard Home';
  }

  private static uniqueBase(snapshotArg: IAdguardSnapshot): string {
    return this.slug(snapshotArg.uniqueId || snapshotArg.host && `${snapshotArg.host}_${snapshotArg.port || ''}` || this.deviceName(snapshotArg));
  }

  private static booleanStatus(valueArg: { enabled?: boolean; enable?: boolean }): boolean {
    return Boolean(valueArg.enabled ?? valueArg.enable);
  }

  private static cleanAttributes<T extends Record<string, unknown>>(attributesArg: T): T {
    return Object.fromEntries(Object.entries(attributesArg).filter(([, valueArg]) => valueArg !== undefined)) as T;
  }
}

const numberOrNull = (valueArg: unknown): number | null => {
  return typeof valueArg === 'number' && Number.isFinite(valueArg) ? valueArg : null;
};

const sum = (valuesArg: number[] | undefined): number => {
  return (valuesArg || []).reduce((totalArg, valueArg) => totalArg + (Number.isFinite(valueArg) ? valueArg : 0), 0);
};

const rounded = (valueArg: number | null): number | null => {
  return valueArg === null ? null : Math.round(valueArg * 100) / 100;
};

const rulesCount = (filtersArg: IAdguardFilterSubscription[] | undefined): number => {
  return (filtersArg || []).reduce((totalArg, filterArg) => totalArg + (typeof filterArg.rules_count === 'number' ? filterArg.rules_count : 0), 0);
};

const blockedPercentage = (snapshotArg: IAdguardSnapshot): number => {
  const queries = numberOrNull(snapshotArg.stats.num_dns_queries) ?? sum(snapshotArg.stats.dns_queries);
  if (!queries) {
    return 0;
  }
  const blocked = numberOrNull(snapshotArg.stats.num_blocked_filtering) ?? sum(snapshotArg.stats.blocked_filtering);
  return rounded(blocked / queries * 100) || 0;
};

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

const booleanValue = (valueArg: unknown): boolean | undefined => {
  return typeof valueArg === 'boolean' ? valueArg : undefined;
};

const isUrlOrAbsolutePath = (valueArg: string): boolean => {
  if (valueArg.startsWith('/')) {
    return true;
  }
  try {
    const parsed = new URL(valueArg);
    return parsed.protocol === 'http:' || parsed.protocol === 'https:';
  } catch {
    return false;
  }
};

const isSwitchKey = (valueArg: unknown): valueArg is TAdguardSwitchKey => {
  return valueArg === 'protection'
    || valueArg === 'filtering'
    || valueArg === 'querylog'
    || valueArg === 'safebrowsing'
    || valueArg === 'safesearch'
    || valueArg === 'parental';
};
