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 { ILidarrConfig, ILidarrLiveRawData, ILidarrQueueItem } from './lidarr.types.js';
import { lidarrDefaultPort, lidarrProfile } from './lidarr.types.js';

export class LidarrMapper {
  public static toSnapshot(optionsArg: Omit<ISimpleLocalSnapshotOptions<ILidarrConfig>, 'profile'>): ISimpleLocalSnapshot {
    if (isLidarrLiveRawData(optionsArg.rawData)) {
      return this.liveSnapshot(optionsArg as ISimpleLocalSnapshotOptions<ILidarrConfig>);
    }
    return SimpleLocalMapper.toSnapshot({ ...optionsArg, profile: lidarrProfile });
  }

  public static toSnapshotFromRaw(configArg: ILidarrConfig, 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(lidarrProfile, snapshotArg);
  }

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

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

  private static liveSnapshot(optionsArg: ISimpleLocalSnapshotOptions<ILidarrConfig>): ISimpleLocalSnapshot {
    const rawData = optionsArg.rawData as ILidarrLiveRawData;
    const host = optionsArg.config.host || hostFromUrl(optionsArg.config.url);
    const port = optionsArg.config.port || portFromUrl(optionsArg.config.url) || (host ? lidarrDefaultPort : undefined);
    const name = optionsArg.config.name || rawData.systemStatus?.instanceName || rawData.systemStatus?.appName || 'Lidarr';
    const online = optionsArg.online ?? true;
    return {
      device: {
        id: optionsArg.config.uniqueId || optionsArg.config.deviceId || (host ? `${host}:${port || ''}` : 'lidarr'),
        name,
        manufacturer: 'Lidarr',
        model: 'Lidarr local integration',
        host,
        port,
        protocol: optionsArg.config.useTls || isHttpsUrl(optionsArg.config.url) ? 'https' : 'http',
        configurationUrl: baseConfigurationUrl(optionsArg.config),
        attributes: cleanAttributes({
          branch: rawData.systemStatus?.branch,
          startTime: rawData.systemStatus?.startTime,
          authentication: rawData.systemStatus?.authentication,
        }),
      },
      entities: this.liveEntities(rawData, online),
      online,
      updatedAt: rawData.fetchedAt || new Date().toISOString(),
      source: optionsArg.source || 'http',
      rawData,
      error: optionsArg.error,
      capabilities: {
        localRead: true,
        localControl: false,
        liveControlRequiresExecutor: true,
      },
    };
  }

  private static liveEntities(rawDataArg: ILidarrLiveRawData, onlineArg: boolean): ISimpleLocalEntitySnapshot[] {
    const entities: ISimpleLocalEntitySnapshot[] = [];
    if (rawDataArg.systemStatus?.version) {
      entities.push({
        id: 'version',
        name: 'Version',
        platform: 'sensor',
        state: rawDataArg.systemStatus.version,
        available: onlineArg,
      });
    }
    if (Array.isArray(rawDataArg.albums)) {
      entities.push({
        id: 'albums',
        name: 'Albums',
        platform: 'sensor',
        state: rawDataArg.albums.length,
        unit: 'albums',
        stateClass: 'total',
        available: onlineArg,
      });
    }
    if (rawDataArg.queue) {
      const records = recordsValue(rawDataArg.queue.records);
      entities.push({
        id: 'queue',
        name: 'Queue',
        platform: 'sensor',
        state: numberValue(rawDataArg.queue.totalRecords) ?? records.length,
        unit: 'albums',
        stateClass: 'total',
        available: onlineArg,
        attributes: cleanAttributes(Object.fromEntries(records.map((itemArg) => [itemArg.title || 'Queue item', queueState(itemArg)]))),
      });
    }
    if (rawDataArg.wanted) {
      const records = recordsValue(rawDataArg.wanted.records);
      entities.push({
        id: 'wanted',
        name: 'Wanted',
        platform: 'sensor',
        state: numberValue(rawDataArg.wanted.totalRecords) ?? records.length,
        unit: 'albums',
        stateClass: 'total',
        available: onlineArg,
        attributes: cleanAttributes(Object.fromEntries(records.map((albumArg) => [albumArg.title || 'Wanted album', albumArg.artist?.artistName]))),
      });
    }
    for (const folder of rawDataArg.rootFolders || []) {
      if (!folder.path) {
        continue;
      }
      const name = basename(folder.path);
      entities.push({
        id: `disk_space_${this.slug(name)}`,
        name: `Disk space ${name}`,
        platform: 'sensor',
        state: folder.accessible === false ? 0 : bytesToGiB(folder.freeSpace),
        unit: 'GiB',
        deviceClass: 'data_size',
        stateClass: 'total',
        available: onlineArg,
        attributes: cleanAttributes({ path: folder.path, accessible: folder.accessible, totalSpaceGiB: bytesToGiB(folder.totalSpace) }),
      });
    }
    return entities.length ? entities : [{ id: 'online', name: 'Online', platform: 'binary_sensor', state: onlineArg, available: true }];
  }
}

const isLidarrLiveRawData = (valueArg: unknown): valueArg is ILidarrLiveRawData => {
  return Boolean(valueArg && typeof valueArg === 'object' && !Array.isArray(valueArg) && ('systemStatus' in valueArg || 'rootFolders' in valueArg || 'queue' in valueArg || 'wanted' in valueArg || 'albums' in valueArg));
};

const recordsValue = <TRecord extends Record<string, unknown>>(valueArg: TRecord[] | undefined): TRecord[] => Array.isArray(valueArg) ? valueArg : [];

const numberValue = (valueArg: unknown): number | undefined => typeof valueArg === 'number' && Number.isFinite(valueArg) ? valueArg : undefined;

const bytesToGiB = (valueArg: unknown): number | undefined => {
  if (typeof valueArg !== 'number' || !Number.isFinite(valueArg)) {
    return undefined;
  }
  return Math.round((valueArg / 1024 ** 3) * 100) / 100;
};

const queueState = (itemArg: ILidarrQueueItem): string => {
  if ((itemArg.sizeleft || 0) > 0 && itemArg.timeleft === '00:00:00') {
    return 'stopped';
  }
  return itemArg.trackedDownloadState || 'unknown';
};

const basename = (pathArg: string): string => pathArg.split(/[\\/]/).filter(Boolean).pop() || pathArg;

const hostFromUrl = (urlArg?: string): string | undefined => parsedUrl(urlArg)?.hostname;

const portFromUrl = (urlArg?: string): number | undefined => {
  const port = parsedUrl(urlArg)?.port;
  return port ? Number(port) : undefined;
};

const isHttpsUrl = (urlArg?: string): boolean => parsedUrl(urlArg)?.protocol === 'https:';

const parsedUrl = (urlArg?: string): URL | undefined => {
  if (!urlArg) {
    return undefined;
  }
  try {
    return new URL(urlArg);
  } catch {
    return undefined;
  }
};

const baseConfigurationUrl = (configArg: ILidarrConfig): string | undefined => {
  if (configArg.url) {
    return configArg.url.replace(/\/+$/, '');
  }
  if (!configArg.host) {
    return undefined;
  }
  const protocol = configArg.useTls ? 'https' : 'http';
  const port = configArg.port || lidarrDefaultPort;
  return `${protocol}://${configArg.host}:${port}`;
};

const 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;
};
