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 { IHolidayConfig } from './holiday.types.js';
import { holidayDefaultName, holidayProfile } from './holiday.types.js';

interface IHolidayEventData {
  date: string;
  name: string;
}

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

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

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

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

  private static normalizeRawData(configArg: IHolidayConfig, rawDataArg: unknown): unknown {
    if (isRecord(rawDataArg) && 'device' in rawDataArg && 'entities' in rawDataArg) {
      return rawDataArg;
    }

    const rawRecord = recordValue(rawDataArg);
    const events = holidayEvents(rawDataArg);
    if (!events.length) {
      return rawDataArg;
    }

    const country = configArg.country || stringValue(rawRecord?.country);
    const province = configArg.province || stringValue(rawRecord?.province ?? rawRecord?.subdivision);
    const categories = configArg.categories || stringArray(rawRecord?.categories);
    const name = configArg.name || stringValue(rawRecord?.name ?? rawRecord?.location) || [country, province].filter(Boolean).join(', ') || holidayDefaultName;
    const id = configArg.uniqueId || SimpleLocalMapper.slug([country, province, ...(categories || [])].filter(Boolean).join('_')) || holidayProfile.domain;
    const today = new Date().toISOString().slice(0, 10);
    const sortedEvents = events.sort((leftArg, rightArg) => leftArg.date.localeCompare(rightArg.date));
    const nextEvent = sortedEvents.find((eventArg) => eventArg.date >= today) || sortedEvents[0];
    const todayEvents = sortedEvents.filter((eventArg) => eventArg.date === today);
    const entities: ISimpleLocalEntitySnapshot[] = [
      {
        id: 'next_holiday',
        uniqueId: `${holidayProfile.domain}_${SimpleLocalMapper.slug(id)}_next_holiday`,
        name: 'Next Holiday',
        platform: 'sensor',
        state: nextEvent?.name || 'none',
        available: true,
        writable: false,
        attributes: {
          start: nextEvent?.date,
          end: nextEvent?.date,
          location: name,
          country,
          province,
          categories,
          upcomingCount: sortedEvents.filter((eventArg) => eventArg.date >= today).length,
        },
      },
      {
        id: 'holiday_today',
        uniqueId: `${holidayProfile.domain}_${SimpleLocalMapper.slug(id)}_holiday_today`,
        name: 'Holiday Today',
        platform: 'binary_sensor',
        state: todayEvents.length > 0,
        available: true,
        writable: false,
        attributes: {
          events: todayEvents.map((eventArg) => eventArg.name),
          date: today,
          country,
          province,
          categories,
        },
      },
    ];

    return {
      device: {
        id,
        name,
        manufacturer: holidayProfile.manufacturer,
        model: holidayProfile.model,
        protocol: holidayProfile.defaultProtocol,
        attributes: {
          country,
          province,
          categories,
          eventCount: sortedEvents.length,
        },
      },
      entities,
      online: configArg.online ?? true,
      updatedAt: new Date().toISOString(),
      source: 'manual',
      rawData: rawDataArg,
    } satisfies ISimpleLocalSnapshot;
  }
}

const holidayEvents = (rawDataArg: unknown): IHolidayEventData[] => {
  if (Array.isArray(rawDataArg)) {
    return eventsFromRecords(rawDataArg.filter(isRecord));
  }
  const rawRecord = recordValue(rawDataArg);
  if (!rawRecord) {
    return [];
  }
  const nested = rawRecord.holidays ?? rawRecord.events ?? rawRecord.calendarEvents;
  if (Array.isArray(nested)) {
    return eventsFromRecords(nested.filter(isRecord));
  }
  if (isRecord(nested)) {
    return eventsFromRecord(nested);
  }
  return eventsFromRecord(rawRecord);
};

const eventsFromRecords = (recordsArg: Array<Record<string, unknown>>): IHolidayEventData[] => recordsArg
  .map((recordArg) => {
    const date = dateValue(recordArg.date ?? recordArg.start ?? recordArg.startDate);
    const name = stringValue(recordArg.name ?? recordArg.summary ?? recordArg.title);
    return date && name ? { date, name } : undefined;
  })
  .filter((eventArg): eventArg is IHolidayEventData => Boolean(eventArg));

const eventsFromRecord = (recordArg: Record<string, unknown>): IHolidayEventData[] => Object.entries(recordArg)
  .map(([dateArg, nameArg]) => isDateString(dateArg) ? { date: dateArg, name: String(nameArg) } : undefined)
  .filter((eventArg): eventArg is IHolidayEventData => Boolean(eventArg));

const dateValue = (valueArg: unknown): string | undefined => {
  if (typeof valueArg !== 'string') {
    return undefined;
  }
  const value = valueArg.slice(0, 10);
  return isDateString(value) ? value : undefined;
};

const isDateString = (valueArg: string): boolean => /^\d{4}-\d{2}-\d{2}$/.test(valueArg);

const stringArray = (valueArg: unknown): string[] | undefined => Array.isArray(valueArg) ? valueArg.filter((itemArg): itemArg is string => typeof itemArg === 'string') : undefined;

const recordValue = (valueArg: unknown): Record<string, unknown> | undefined => isRecord(valueArg) ? valueArg : undefined;

const isRecord = (valueArg: unknown): valueArg is Record<string, unknown> => Boolean(valueArg && typeof valueArg === 'object' && !Array.isArray(valueArg));

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