import Logger from 'src/application/logging/logger';
import { EnviveMetricsEventName } from '../amplitudeService/eventNames';
import { GA4ProjectedEventConfig, GA4_EVENT_SCHEMA } from './ga4EventSchema';

const logger = new Logger('ga4ProjectionService');

const filterToSchema = (
  eventProps: Record<string, unknown>,
  allowedFields: readonly string[],
): Record<string, unknown> => {
  const result: Record<string, unknown> = {};
  for (const field of allowedFields) {
    if (field in eventProps) {
      result[field] = eventProps[field];
    }
  }
  return result;
};

// Flatten only true top-level nested objects (keys with no dots) one level deep.
// Keys that already contain dots are left intact so that e.g.
// `trigger.widget_interaction_data: { ... }` is preserved for downstream extraction.
const flattenOneLevel = (obj: Record<string, unknown>): Record<string, unknown> => {
  const result: Record<string, unknown> = {};
  for (const [key, value] of Object.entries(obj)) {
    if (
      !key.includes('.') &&
      value !== null &&
      typeof value === 'object' &&
      !Array.isArray(value)
    ) {
      for (const [subKey, subValue] of Object.entries(value as Record<string, unknown>)) {
        result[`${key}.${subKey}`] = subValue;
      }
    } else {
      result[key] = value;
    }
  }
  return result;
};

// Apply a GA4 projection map { ga4Key: sourceKey } to extract and rename fields.
const filterWithProjections = (
  eventProps: Record<string, unknown>,
  projections: Record<string, string>,
): Record<string, unknown> => {
  const result: Record<string, unknown> = {};
  for (const [ga4Key, sourceKey] of Object.entries(projections)) {
    if (sourceKey in eventProps) {
      result[ga4Key] = eventProps[sourceKey];
    }
  }
  return result;
};

// "context.page_type" → "context_page_type"
const flattenDotKeys = (obj: Record<string, unknown>): Record<string, unknown> => {
  const result: Record<string, unknown> = {};
  for (const [key, value] of Object.entries(obj)) {
    result[key.replace(/\./g, '_')] = value;
  }
  return result;
};

// Omit context.page_id for non-pdp/plp page types. The current implementation for context.page_id is:
// PDP: product_id
// PLP: plp_id
// Search: search query
// Other: page url
// We want to omit all but pdp and plp page types to provide a clear, consistent interface for merchants.
// Handles both dot-notation keys (allowedFields path) and renamed GA4 keys (fieldProjections path).
const sanitizePageId = (filtered: Record<string, unknown>): Record<string, unknown> => {
  const pageType = filtered['page_type'] ?? filtered['context.page_type'];
  if (pageType === 'pdp' || pageType === 'plp') {
    return filtered;
  }
  const rest = { ...filtered };
  delete rest['page_id'];
  delete rest['context.page_id'];
  return rest;
};

const getNestedValue = (obj: Record<string, unknown>, path: string): unknown => {
  const parts = path.split('.');
  let current: unknown = obj;
  for (const part of parts) {
    if (current === null || typeof current !== 'object') return undefined;
    current = (current as Record<string, unknown>)[part];
  }
  return current;
};

// Extract whitelisted sub-fields from trigger.widget_interaction_data
const projectWidgetInteractionData = (
  eventProps: Record<string, unknown>,
  config: GA4ProjectedEventConfig,
): Record<string, unknown> => {
  if (!config.widgetInteractionDataProjections) {
    return {};
  }

  const interaction = eventProps['trigger.widget_interaction'];
  if (typeof interaction !== 'string') {
    return {};
  }

  const projectionMap = config.widgetInteractionDataProjections[interaction];
  if (!projectionMap) {
    return {};
  }

  const interactionData = eventProps['trigger.widget_interaction_data'];
  if (
    interactionData === null ||
    interactionData === undefined ||
    typeof interactionData !== 'object'
  ) {
    return {};
  }

  const data = interactionData as Record<string, unknown>;
  const result: Record<string, unknown> = {};
  for (const [gaKey, sourcePath] of Object.entries(projectionMap)) {
    const value = getNestedValue(data, sourcePath);
    if (value !== undefined) {
      result[gaKey] = value;
    }
  }
  return result;
};

const truncateString = (value: unknown): unknown => {
  if (typeof value === 'string' && value.length > 100) {
    return `${value.substring(0, 97)}...`;
  }
  return value;
};

const truncateValues = (obj: Record<string, unknown>): Record<string, unknown> => {
  const result: Record<string, unknown> = {};
  for (const [key, value] of Object.entries(obj)) {
    result[key] = truncateString(value);
  }
  return result;
};

const pushToDataLayer = (gaEvent: Record<string, unknown>): void => {
  if (typeof window !== 'undefined' && window.dataLayer) {
    window.dataLayer.push(gaEvent);
  }
};

export const projectToGA4 = (
  eventName: EnviveMetricsEventName,
  eventProps?: Record<string, unknown>,
): void => {
  try {
    const schemaEntry = GA4_EVENT_SCHEMA[eventName];

    if (schemaEntry.gaEventName === null) {
      return;
    }

    const config = schemaEntry;
    const props = flattenOneLevel(eventProps ?? {});

    let filtered: Record<string, unknown>;
    if (config.fieldProjections) {
      filtered = filterWithProjections(props, config.fieldProjections);
    } else {
      filtered = filterToSchema(props, config.allowedFields);
    }
    filtered = sanitizePageId(filtered);

    const interactionFields = projectWidgetInteractionData(props, config);

    const flatParams = {
      ...flattenDotKeys(filtered),
      ...interactionFields,
    };

    const truncatedParams = truncateValues(flatParams);

    pushToDataLayer({
      event: config.gaEventName,
      ...truncatedParams,
    });
  } catch (err) {
    logger.logError('Error projecting event to GA4', err, {
      eventName,
    });
  }
};
