import type * as shxInterfaces from '@smarthome.exchange/interfaces';
import { SimpleLocalMapper, type IIntegrationEntity, type ISimpleLocalSnapshot, type ISimpleLocalSnapshotOptions, type TSimpleLocalRawData } from '../../core/index.js';
import type { IMealieConfig, IMealieRawData } from './mealie.types.js';
import { mealieProfile } from './mealie.types.js';

export class MealieMapper {
  public static toSnapshot(optionsArg: Omit<ISimpleLocalSnapshotOptions<IMealieConfig>, 'profile'>): ISimpleLocalSnapshot {
    if (this.isMealieRawData(optionsArg.rawData)) {
      return this.toMealieSnapshot(optionsArg.config, optionsArg.rawData, optionsArg.online, optionsArg.source, optionsArg.error);
    }
    return SimpleLocalMapper.toSnapshot({ ...optionsArg, profile: mealieProfile });
  }

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

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

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

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

  private static toMealieSnapshot(configArg: IMealieConfig, rawDataArg: IMealieRawData, onlineArg = true, sourceArg: ISimpleLocalSnapshot['source'] = 'http', errorArg?: string): ISimpleLocalSnapshot {
    const endpoint = parseEndpoint(configArg.url || configArg.host, configArg.port, configArg.useTls);
    const userId = stringValue(rawDataArg.user?.id) || stringValue(rawDataArg.user?.user_id);
    const username = stringValue(rawDataArg.user?.username) || stringValue(rawDataArg.user?.fullName) || stringValue(rawDataArg.user?.full_name);
    const version = stringValue(rawDataArg.about?.version);
    const entities = this.entitiesFromRaw(rawDataArg, onlineArg);

    return {
      device: {
        id: configArg.uniqueId || configArg.deviceId || userId || (endpoint?.host ? `${endpoint.host}:${endpoint.port || ''}` : mealieProfile.domain),
        name: configArg.name || username || mealieProfile.defaultName || mealieProfile.displayName,
        manufacturer: mealieProfile.manufacturer,
        model: version ? `Mealie ${version}` : 'Mealie server',
        serialNumber: userId,
        host: endpoint?.host,
        port: endpoint?.port,
        protocol: endpoint?.protocol,
        configurationUrl: endpoint?.baseUrl,
        attributes: cleanAttributes({
          version,
          username: stringValue(rawDataArg.user?.username),
          email: stringValue(rawDataArg.user?.email),
          source: sourceArg,
          errors: rawDataArg.errors,
          ...configArg.metadata,
        }),
      },
      entities,
      online: onlineArg,
      updatedAt: rawDataArg.fetchedAt || new Date().toISOString(),
      source: sourceArg,
      rawData: rawDataArg,
      error: errorArg,
      capabilities: {
        localRead: true,
        localControl: true,
        liveControlRequiresExecutor: false,
      },
    };
  }

  private static entitiesFromRaw(rawDataArg: IMealieRawData, onlineArg: boolean): ISimpleLocalSnapshot['entities'] {
    const statistics = rawDataArg.statistics || {};
    const entities: ISimpleLocalSnapshot['entities'] = [
      statisticEntity('recipes', 'Recipes', numberValue(statistics.totalRecipes ?? statistics.total_recipes), onlineArg),
      statisticEntity('users', 'Users', numberValue(statistics.totalUsers ?? statistics.total_users), onlineArg),
      statisticEntity('categories', 'Categories', numberValue(statistics.totalCategories ?? statistics.total_categories), onlineArg),
      statisticEntity('tags', 'Tags', numberValue(statistics.totalTags ?? statistics.total_tags), onlineArg),
      statisticEntity('tools', 'Tools', numberValue(statistics.totalTools ?? statistics.total_tools), onlineArg),
    ].filter((entityArg): entityArg is ISimpleLocalSnapshot['entities'][number] => entityArg.state !== undefined);

    const mealplanItems = arrayValue(rawDataArg.mealplans?.items);
    if (mealplanItems) {
      entities.push({ id: 'mealplans', name: 'Mealplans', platform: 'sensor', state: mealplanItems.length, available: onlineArg, stateClass: 'total', attributes: { key: 'mealplans' } });
    }

    const shoppingLists = arrayValue(rawDataArg.shoppingLists?.items);
    if (shoppingLists) {
      entities.push({ id: 'shopping_lists', name: 'Shopping lists', platform: 'sensor', state: shoppingLists.length, available: onlineArg, stateClass: 'total', attributes: { key: 'shopping_lists' } });
    }

    const shoppingItemCount = rawDataArg.shoppingItems
      ? Object.values(rawDataArg.shoppingItems).reduce((countArg, responseArg) => countArg + (arrayValue(responseArg.items)?.length || 0), 0)
      : undefined;
    if (shoppingItemCount !== undefined) {
      entities.push({ id: 'shopping_items', name: 'Shopping items', platform: 'sensor', state: shoppingItemCount, available: onlineArg, stateClass: 'total', attributes: { key: 'shopping_items' } });
    }

    return entities.length ? entities : [{ id: 'online', name: 'Online', platform: 'binary_sensor', state: onlineArg, available: true }];
  }

  private static isMealieRawData(valueArg: unknown): valueArg is IMealieRawData {
    return Boolean(valueArg && typeof valueArg === 'object' && !Array.isArray(valueArg) && (
      'about' in valueArg || 'user' in valueArg || 'statistics' in valueArg || 'mealplans' in valueArg || 'shoppingLists' in valueArg || 'shoppingItems' in valueArg
    ));
  }
}

const statisticEntity = (idArg: string, nameArg: string, valueArg: number | undefined, onlineArg: boolean): ISimpleLocalSnapshot['entities'][number] => ({
  id: idArg,
  name: nameArg,
  platform: 'sensor',
  state: valueArg,
  available: onlineArg,
  stateClass: 'total',
  attributes: { key: idArg },
});

const parseEndpoint = (valueArg: string | undefined, portArg?: number, useTlsArg?: boolean): { baseUrl: string; host: string; port?: number; protocol: 'http' | 'https' } | undefined => {
  if (!valueArg) {
    return undefined;
  }
  const protocol = useTlsArg ? 'https' : 'http';
  try {
    const url = new URL(/^[a-z][a-z0-9+.-]*:\/\//i.test(valueArg) ? valueArg : `${protocol}://${valueArg}`);
    if (portArg) {
      url.port = String(portArg);
    }
    return {
      baseUrl: url.toString().replace(/\/$/, ''),
      host: url.hostname,
      port: portArg || (url.port ? Number(url.port) : undefined),
      protocol: url.protocol === 'https:' ? 'https' : 'http',
    };
  } catch {
    return undefined;
  }
};

const arrayValue = (valueArg: unknown): unknown[] | undefined => Array.isArray(valueArg) ? valueArg : 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 stringValue = (valueArg: unknown): string | undefined => typeof valueArg === 'string' && valueArg.trim() ? valueArg.trim() : undefined;

const cleanAttributes = (valueArg: Record<string, unknown>): Record<string, unknown> | undefined => {
  const clean = Object.fromEntries(Object.entries(valueArg).filter(([, entryArg]) => entryArg !== undefined));
  return Object.keys(clean).length ? clean : undefined;
};
