import type * as shxInterfaces from '@smarthome.exchange/interfaces';
import { SimpleLocalMapper, type IIntegrationEntity, type ISimpleLocalEntitySnapshot, type ISimpleLocalSnapshot, type ISimpleLocalSnapshotOptions, type TSimpleLocalRawData } from '../../core/index.js';
import type { INzbgetConfig, INzbgetRawData } from './nzbget.types.js';
import { nzbgetDefaultPort, nzbgetProfile } from './nzbget.types.js';

export class NzbgetMapper {
  public static toSnapshot(optionsArg: Omit<ISimpleLocalSnapshotOptions<INzbgetConfig>, 'profile'>): ISimpleLocalSnapshot {
    const rawData = optionsArg.rawData ?? optionsArg.config.rawData;
    if (this.isNativeRawData(rawData)) {
      return this.toNativeSnapshot(optionsArg.config, rawData, optionsArg.online ?? true, optionsArg.source || 'manual', optionsArg.error);
    }
    return SimpleLocalMapper.toSnapshot({ ...optionsArg, profile: nzbgetProfile });
  }

  public static toSnapshotFromRaw(configArg: INzbgetConfig, rawDataArg: TSimpleLocalRawData): ISimpleLocalSnapshot {
    return this.toSnapshot({ config: configArg, rawData: rawDataArg, online: true, source: 'manual' });
  }

  public static toDevices(snapshotArg: ISimpleLocalSnapshot): shxInterfaces.data.IDeviceDefinition[] {
    return SimpleLocalMapper.toDevices(nzbgetProfile, snapshotArg);
  }

  public static toEntities(snapshotArg: ISimpleLocalSnapshot): IIntegrationEntity[] {
    return SimpleLocalMapper.toEntities(nzbgetProfile, snapshotArg);
  }

  public static slug(valueArg: unknown): string {
    return SimpleLocalMapper.slug(valueArg);
  }

  private static toNativeSnapshot(configArg: INzbgetConfig, rawDataArg: INzbgetRawData, onlineArg: boolean, sourceArg: ISimpleLocalSnapshot['source'], errorArg?: string): ISimpleLocalSnapshot {
    const endpoint = this.endpointInfo(configArg);
    const status = this.recordValue(rawDataArg.status) || {};
    const history = this.arrayRecords(rawDataArg.downloads || rawDataArg.history);
    const downloadPaused = this.booleanValue(status.DownloadPaused);
    const postPaused = this.booleanValue(status.PostPaused);
    const uptimeSeconds = this.numberValue(status.UpTimeSec);
    const entities: ISimpleLocalEntitySnapshot[] = [];

    this.pushEntity(entities, 'article_cache_mb', 'Article Cache', this.numberValue(status.ArticleCacheMB), { unit: 'MB', deviceClass: 'data_size' });
    this.pushEntity(entities, 'average_download_rate', 'Average Speed', this.numberValue(status.AverageDownloadRate), { unit: 'B/s', deviceClass: 'data_rate' });
    this.pushEntity(entities, 'download_paused', 'Download Paused', downloadPaused, { platform: 'sensor' });
    this.pushEntity(entities, 'download_rate', 'Speed', this.numberValue(status.DownloadRate), { unit: 'B/s', deviceClass: 'data_rate' });
    this.pushEntity(entities, 'downloaded_size_mb', 'Downloaded Size', this.numberValue(status.DownloadedSizeMB), { unit: 'MB', deviceClass: 'data_size' });
    this.pushEntity(entities, 'free_disk_space_mb', 'Disk Free', this.numberValue(status.FreeDiskSpaceMB), { unit: 'MB', deviceClass: 'data_size' });
    this.pushEntity(entities, 'post_job_count', 'Post Processing Jobs', this.numberValue(status.PostJobCount), { unit: 'Jobs' });
    this.pushEntity(entities, 'post_paused', 'Post Processing Paused', postPaused, { platform: 'sensor' });
    this.pushEntity(entities, 'remaining_size_mb', 'Queue Size', this.numberValue(status.RemainingSizeMB), { unit: 'MB', deviceClass: 'data_size' });
    this.pushEntity(entities, 'uptime', 'Uptime', uptimeSeconds !== undefined ? new Date(Date.now() - uptimeSeconds * 1000).toISOString() : undefined, { deviceClass: 'timestamp' });
    this.pushEntity(entities, 'download_limit', 'Speed Limit', this.numberValue(status.DownloadLimit), { unit: 'B/s', deviceClass: 'data_rate' });
    this.pushEntity(entities, 'history_count', 'History Items', history.length, { unit: 'downloads' });

    if (downloadPaused !== undefined) {
      entities.push({
        id: 'download',
        name: 'Download',
        platform: 'switch',
        state: !downloadPaused,
        writable: true,
        available: onlineArg,
        attributes: { paused: downloadPaused, source: sourceArg },
      });
    }

    if (!entities.length) {
      entities.push({ id: 'online', name: 'Online', platform: 'binary_sensor', state: onlineArg, available: true, attributes: { source: sourceArg } });
    }

    return {
      device: {
        id: configArg.uniqueId || configArg.deviceId || (endpoint.host ? `${endpoint.host}:${endpoint.port || nzbgetDefaultPort}` : undefined) || configArg.name || nzbgetProfile.domain,
        name: configArg.name || nzbgetProfile.defaultName || nzbgetProfile.displayName,
        manufacturer: nzbgetProfile.manufacturer,
        model: rawDataArg.version ? `NZBGet ${rawDataArg.version}` : nzbgetProfile.model,
        host: endpoint.host,
        port: endpoint.port,
        protocol: endpoint.protocol,
        configurationUrl: endpoint.configurationUrl,
        attributes: this.cleanAttributes({ version: rawDataArg.version, fetchedAt: rawDataArg.fetchedAt, ...configArg.metadata }),
      },
      entities,
      online: onlineArg,
      updatedAt: new Date().toISOString(),
      source: sourceArg,
      rawData: this.clone(rawDataArg),
      error: errorArg,
      capabilities: {
        localRead: true,
        localControl: true,
        protocol: 'xmlrpc',
      },
    };
  }

  private static pushEntity(entitiesArg: ISimpleLocalEntitySnapshot[], idArg: string, nameArg: string, valueArg: unknown, optionsArg: { platform?: ISimpleLocalEntitySnapshot['platform']; unit?: string; deviceClass?: string } = {}): void {
    if (valueArg === undefined) {
      return;
    }
    entitiesArg.push({
      id: idArg,
      name: nameArg,
      platform: optionsArg.platform || 'sensor',
      state: valueArg,
      unit: optionsArg.unit,
      deviceClass: optionsArg.deviceClass,
      available: true,
      writable: false,
    });
  }

  private static isNativeRawData(valueArg: unknown): valueArg is INzbgetRawData {
    if (!valueArg || typeof valueArg !== 'object' || Array.isArray(valueArg)) {
      return false;
    }
    const value = valueArg as Record<string, unknown>;
    if ('device' in value && 'entities' in value) {
      return false;
    }
    return 'status' in value || 'downloads' in value || 'history' in value || 'version' in value;
  }

  private static endpointInfo(configArg: INzbgetConfig): { host?: string; port?: number; protocol: 'http' | 'https'; configurationUrl?: string } {
    const parsed = this.parseUrl(configArg.url);
    const useTls = this.booleanValue(configArg.useTls) || this.booleanValue(configArg.ssl) || parsed?.protocol === 'https:';
    const protocol = useTls ? 'https' : 'http';
    const host = configArg.host || parsed?.hostname;
    const port = configArg.port || (parsed?.port ? Number(parsed.port) : undefined) || (host ? nzbgetDefaultPort : undefined);
    const portPart = port && !((protocol === 'https' && port === 443) || (protocol === 'http' && port === 80)) ? `:${port}` : '';
    return { host, port, protocol, configurationUrl: host ? `${protocol}://${host}${portPart}` : undefined };
  }

  private static parseUrl(valueArg: string | undefined): URL | undefined {
    if (!valueArg) {
      return undefined;
    }
    try {
      return new URL(valueArg);
    } catch {
      return undefined;
    }
  }

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

  private static arrayRecords(valueArg: unknown): Array<Record<string, unknown>> {
    return Array.isArray(valueArg) ? valueArg.filter((itemArg): itemArg is Record<string, unknown> => Boolean(itemArg && typeof itemArg === 'object' && !Array.isArray(itemArg))) : [];
  }

  private static 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;
  }

  private static booleanValue(valueArg: unknown): boolean | undefined {
    if (typeof valueArg === 'boolean') {
      return valueArg;
    }
    if (typeof valueArg === 'number') {
      return valueArg !== 0;
    }
    if (typeof valueArg === 'string') {
      const normalized = valueArg.trim().toLowerCase();
      if (['true', 'yes', 'on', '1'].includes(normalized)) {
        return true;
      }
      if (['false', 'no', 'off', '0'].includes(normalized)) {
        return false;
      }
    }
    return undefined;
  }

  private static 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;
  }

  private static clone<TValue>(valueArg: TValue): TValue {
    return JSON.parse(JSON.stringify(valueArg)) as TValue;
  }
}
