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 { IPaperlessNgxConfig, IPaperlessNgxRawApiData, TPaperlessNgxRawData } from './paperless_ngx.types.js';
import { paperlessNgxDefaultName, paperlessNgxProfile } from './paperless_ngx.types.js';

export class PaperlessNgxMapper {
  public static toSnapshot(optionsArg: Omit<ISimpleLocalSnapshotOptions<IPaperlessNgxConfig>, 'profile'>): ISimpleLocalSnapshot {
    return SimpleLocalMapper.toSnapshot({ ...optionsArg, profile: paperlessNgxProfile, rawData: this.normalizeRawData(optionsArg.rawData, optionsArg.config) });
  }

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

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

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

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

  public static normalizeRawData(rawDataArg: unknown, configArg: IPaperlessNgxConfig): unknown {
    const parsedRawData = parseMaybeJson(rawDataArg);
    const rawObject = objectValue(parsedRawData);
    if (!rawObject || rawObject.device || Array.isArray(rawObject.entities)) {
      return parsedRawData;
    }

    const statistics = objectValue(rawObject.statistics);
    const status = objectValue(rawObject.status);
    const remoteVersion = objectValue(rawObject.remoteVersion);
    if (!statistics && !status && !remoteVersion) {
      return parsedRawData;
    }

    const endpoint = endpointFromConfig(configArg);
    const version = stringValue(status?.pngx_version) || stringValue(rawObject.hostVersion);
    const latestVersion = stringValue(remoteVersion?.version)?.replace(/^v/i, '');
    const entities: ISimpleLocalEntitySnapshot[] = [];

    addNumberEntity(entities, 'documents_total', 'Documents Total', statistics?.documents_total);
    addNumberEntity(entities, 'documents_inbox', 'Documents Inbox', statistics?.documents_inbox);
    addNumberEntity(entities, 'characters_count', 'Characters Count', statistics?.character_count, undefined, { entityCategory: 'diagnostic' });
    addNumberEntity(entities, 'tag_count', 'Tag Count', statistics?.tag_count, undefined, { entityCategory: 'diagnostic' });
    addNumberEntity(entities, 'correspondent_count', 'Correspondent Count', statistics?.correspondent_count, undefined, { entityCategory: 'diagnostic' });
    addNumberEntity(entities, 'document_type_count', 'Document Type Count', statistics?.document_type_count, undefined, { entityCategory: 'diagnostic' });

    const storage = objectValue(status?.storage);
    addNumberEntity(entities, 'storage_total', 'Storage Total', bytesToGigabytes(storage?.total), 'GB', { deviceClass: 'data_size', entityCategory: 'diagnostic' });
    addNumberEntity(entities, 'storage_available', 'Storage Available', bytesToGigabytes(storage?.available), 'GB', { deviceClass: 'data_size', entityCategory: 'diagnostic' });

    const database = objectValue(status?.database);
    const tasks = objectValue(status?.tasks);
    addStatusEntity(entities, 'database_status', 'Database Status', database?.status);
    addStatusEntity(entities, 'index_status', 'Index Status', tasks?.index_status);
    addStatusEntity(entities, 'classifier_status', 'Classifier Status', tasks?.classifier_status);
    addStatusEntity(entities, 'celery_status', 'Celery Status', tasks?.celery_status);
    addStatusEntity(entities, 'redis_status', 'Redis Status', tasks?.redis_status);
    addStatusEntity(entities, 'sanity_check_status', 'Sanity Check Status', tasks?.sanity_check_status);

    if (version || latestVersion) {
      entities.push({
        id: 'paperless_update',
        name: 'Paperless Update',
        platform: 'update',
        state: latestVersion && version ? latestVersion !== version : false,
        attributes: cleanAttributes({ installedVersion: version, latestVersion, releaseUrl: 'https://docs.paperless-ngx.com/changelog/' }),
      });
    }

    return {
      device: {
        id: configArg.uniqueId || configArg.deviceId || endpoint?.host || paperlessNgxDefaultName,
        name: configArg.name || endpoint?.host || paperlessNgxDefaultName,
        manufacturer: 'Paperless-ngx',
        model: version ? `Paperless-ngx ${version}` : 'Paperless-ngx',
        serialNumber: endpoint?.host,
        host: endpoint?.host,
        port: endpoint?.port,
        protocol: endpoint?.protocol || 'https',
        configurationUrl: endpoint?.baseUrl,
        attributes: cleanAttributes({ statusError: rawObject.statusError, remoteVersionError: rawObject.remoteVersionError }),
      },
      entities,
      online: true,
      updatedAt: stringValue(rawObject.fetchedAt) || new Date().toISOString(),
      source: 'http',
      rawData: parsedRawData,
    } satisfies ISimpleLocalSnapshot;
  }
}

const addNumberEntity = (entitiesArg: ISimpleLocalEntitySnapshot[], idArg: string, nameArg: string, valueArg: unknown, unitArg?: string, attributesArg: Record<string, unknown> = {}): void => {
  const value = numberValue(valueArg);
  if (value === undefined) {
    return;
  }
  entitiesArg.push({
    id: idArg,
    name: nameArg,
    platform: 'sensor',
    state: value,
    unit: unitArg,
    stateClass: 'measurement',
    attributes: cleanAttributes({ key: idArg, ...attributesArg }),
  });
};

const addStatusEntity = (entitiesArg: ISimpleLocalEntitySnapshot[], idArg: string, nameArg: string, valueArg: unknown): void => {
  const status = stringValue(valueArg)?.toLowerCase();
  if (!status || status === 'unknown') {
    return;
  }
  entitiesArg.push({
    id: idArg,
    name: nameArg,
    platform: 'sensor',
    state: status,
    deviceClass: 'enum',
    attributes: { key: idArg, entityCategory: 'diagnostic' },
  });
};

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

const objectValue = (valueArg: unknown): Record<string, unknown> | undefined => {
  return valueArg && typeof valueArg === 'object' && !Array.isArray(valueArg) ? valueArg as Record<string, unknown> : 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 bytesToGigabytes = (valueArg: unknown): number | undefined => {
  const bytes = numberValue(valueArg);
  return bytes === undefined ? undefined : Math.round((bytes / 1_000_000_000) * 100) / 100;
};

const endpointFromConfig = (configArg: IPaperlessNgxConfig): { baseUrl: string; host: string; port?: number; protocol: 'http' | 'https' } | undefined => {
  const url = stringValue(configArg.url) || stringValue(configArg.host);
  if (!url) {
    return undefined;
  }
  try {
    const parsed = new URL(url.includes('://') ? url : `${configArg.useTls === false ? 'http' : 'https'}://${url}`);
    if (configArg.port) {
      parsed.port = String(configArg.port);
    }
    parsed.pathname = parsed.pathname.replace(/\/+$/, '');
    parsed.search = '';
    parsed.hash = '';
    return {
      baseUrl: parsed.toString().replace(/\/$/, ''),
      host: parsed.hostname,
      port: parsed.port ? Number(parsed.port) : undefined,
      protocol: parsed.protocol === 'http:' ? 'http' : 'https',
    };
  } catch {
    return undefined;
  }
};

const cleanAttributes = (attributesArg: Record<string, unknown>): Record<string, unknown> => {
  return Object.fromEntries(Object.entries(attributesArg).filter(([, valueArg]) => valueArg !== undefined && valueArg !== null && valueArg !== ''));
};
