import { FrontendConfig } from 'src/application/models/frontendConfig';
import { transformSnakeToCamel } from 'src/application/models/utils/snakeToCamelTransformer';
import {
  PageVariantConfig,
  PageVariantTestType,
  WidgetConfig,
  WidgetMountingConfig,
} from 'src/contexts/types';
import { DOMInsertionType } from 'src/merchants/domInsertion';
import { GridInsertionType } from 'src/merchants/gridInsertion';
import Logger from 'src/application/logging/logger';

const logger = new Logger('validateGraphQLFrontendConfig');

// Helper function to validate and transform PageVariantQueryData to PageVariantConfig
export const validateAndTransformPageVariants = (
  config: Record<string, unknown>,
): PageVariantConfig => {
  const transformed = transformSnakeToCamel(config) as Record<string, unknown>;

  const { variantChecks, widgetMounting, ...rest } = transformed;

  // Convert variantChecks to variantTests with testType normalization
  const variantTests = Array.isArray(variantChecks)
    ? variantChecks.map(({ checkType, ...restCheck }) => ({
        ...restCheck,
        testType: checkType,
      }))
    : [];

  // Normalize widgetMounting: checks -> tests, checkType -> testType
  const normalizedWidgetMounting = Array.isArray(widgetMounting)
    ? widgetMounting.map(({ mountingPointWidgets, ...restMounting }) => ({
        ...restMounting,
        ...(Array.isArray(mountingPointWidgets)
          ? {
              mountingPointWidgets: mountingPointWidgets.map(({ checks, ...restWidget }) => ({
                ...restWidget,
                tests: Array.isArray(checks)
                  ? checks.map(({ checkType, ...restCheck }) => ({
                      ...restCheck,
                      testType: checkType,
                    }))
                  : [],
              })),
            }
          : {}),
      }))
    : [];

  const normalizedConfig: Record<string, unknown> = {
    ...rest,
    ...(variantTests ? { variantTests } : {}),
    ...(normalizedWidgetMounting ? { widgetMounting: normalizedWidgetMounting } : {}),
  };

  const variantId = normalizedConfig.variantId as string;
  const variantType = normalizedConfig.variantType as string;

  if (typeof variantId !== 'string' || !variantId) {
    throw new Error(`Missing or invalid variantId for pageVariant`);
  }

  if (typeof variantType !== 'string' || !variantType) {
    throw new Error(`Missing or invalid variantType for pageVariant '${variantId}'`);
  }

  if (!Array.isArray(variantTests)) {
    throw new Error(
      `Missing or invalid variantTests for pageVariant '${variantId}'. Must be an array`,
    );
  }

  for (const test of variantTests) {
    if (!test.testType || typeof test.testType !== 'string') {
      throw new Error(`Invalid variantTest.testType for pageVariant '${variantId}'`);
    }
    if (!Object.values(PageVariantTestType).includes(test.testType as PageVariantTestType)) {
      throw new Error(
        `Invalid testType '${test.testType}' for pageVariant '${variantId}'. Valid types: ${Object.values(
          PageVariantTestType,
        ).join(', ')}`,
      );
    }
  }

  if (!Array.isArray(normalizedWidgetMounting)) {
    throw new Error(
      `Missing or invalid widgetMounting for pageVariant '${variantId}'. Must be an array`,
    );
  }

  for (const mounting of normalizedWidgetMounting) {
    if (typeof mounting.mountingConfigId !== 'string' || !mounting.mountingConfigId) {
      throw new Error(`Invalid mountingConfigId in widgetMounting for pageVariant '${variantId}'`);
    }

    if (mounting.widgetConfigId && typeof mounting.widgetConfigId !== 'string') {
      throw new Error(`Invalid widgetConfigId in widgetMounting for pageVariant '${variantId}'`);
    }

    if (mounting.mountingPointWidgets) {
      for (const widget of mounting.mountingPointWidgets) {
        if (typeof widget.widgetConfigId !== 'string' || !widget.widgetConfigId) {
          throw new Error(
            `Invalid widgetConfigId in mountingPointWidgets for pageVariant '${variantId}'`,
          );
        }

        if (!Array.isArray(widget.tests)) {
          throw new Error(
            `Invalid or missing tests in mountingPointWidgets for pageVariant '${variantId}'`,
          );
        }
      }
    }
  }

  if (variantType === 'plp') {
    if (typeof normalizedConfig.plpIdExtractor !== 'string' || !normalizedConfig.plpIdExtractor) {
      throw new Error(`Missing or invalid plpIdExtractor for PLP pageVariant '${variantId}'`);
    }
  }

  if (variantType === 'pdp') {
    if (
      typeof normalizedConfig.productIdExtractor !== 'string' ||
      !normalizedConfig.productIdExtractor
    ) {
      throw new Error(`Missing or invalid productIdExtractor for PDP pageVariant '${variantId}'`);
    }
    if (
      normalizedConfig.parentProductIdExtractor &&
      typeof normalizedConfig.parentProductIdExtractor !== 'string'
    ) {
      throw new Error(
        `Invalid parentProductIdExtractor for PDP pageVariant '${variantId}'. Must be string if provided`,
      );
    }
  }

  return normalizedConfig as PageVariantConfig;
};

// Helper function to validate and transform MountingConfigQueryData to WidgetMountingConfig
export const validateAndTransformMountingConfig = (
  config: Record<string, unknown>,
  configKey: string,
): WidgetMountingConfig => {
  // RENAMING LOGIC: Convert backend "Check" terminology to frontend "Test" terminology
  const normalizedConfig = { ...config };

  // Convert checkId to testId for frontend compatibility (data is already camelCase)
  if (normalizedConfig.checkId && !normalizedConfig.testId) {
    normalizedConfig.testId = normalizedConfig.checkId;
    delete normalizedConfig.checkId;
  }

  // Required fields - these must be present and valid
  if (typeof normalizedConfig.mountingConfigId !== 'string' || !normalizedConfig.mountingConfigId) {
    throw new Error(`Missing or invalid mountingConfigId for config '${configKey}'`);
  }
  if (typeof normalizedConfig.containerId !== 'string' || !normalizedConfig.containerId) {
    throw new Error(`Missing or invalid containerId for config '${configKey}'`);
  }
  if (typeof normalizedConfig.testId !== 'string' || !normalizedConfig.testId) {
    throw new Error(`Missing or invalid testId for config '${configKey}'`);
  }

  // Required insertionPoint
  if (
    !normalizedConfig.insertionPoint ||
    typeof normalizedConfig.insertionPoint !== 'object' ||
    Array.isArray(normalizedConfig.insertionPoint)
  ) {
    throw new Error(`Missing or invalid insertionPoint for config '${configKey}'`);
  }

  const insertionPoint = normalizedConfig.insertionPoint as Record<string, unknown>;

  // Validate insertionPoint required fields
  if (typeof insertionPoint.selector !== 'string' || !insertionPoint.selector) {
    throw new Error(`Missing or invalid insertionPoint.selector for config '${configKey}'`);
  }
  if (typeof insertionPoint.insertionType !== 'string' || !insertionPoint.insertionType) {
    throw new Error(`Missing or invalid insertionPoint.insertionType for config '${configKey}'`);
  }

  // Validate insertionType is a valid DOMInsertionType
  if (!Object.values(DOMInsertionType).includes(insertionPoint.insertionType as DOMInsertionType)) {
    throw new Error(
      `Invalid insertionPoint.insertionType '${insertionPoint.insertionType}' for config '${configKey}'. Valid types: ${Object.values(DOMInsertionType).join(', ')}`,
    );
  }

  // Validate optional insertionPoint fields
  if (insertionPoint.selectionIndex != null && typeof insertionPoint.selectionIndex !== 'number') {
    throw new Error(
      `Invalid insertionPoint.selectionIndex for config '${configKey}'. Must be a number or null/undefined`,
    );
  }

  // Validate optional parentInsertionPoint if present
  let parentInsertionPoint;
  if (normalizedConfig.parentInsertionPoint) {
    if (
      typeof normalizedConfig.parentInsertionPoint !== 'object' ||
      Array.isArray(normalizedConfig.parentInsertionPoint)
    ) {
      throw new Error(`Invalid parentInsertionPoint for config '${configKey}'. Must be an object`);
    }

    const parentPoint = normalizedConfig.parentInsertionPoint as Record<string, unknown>;
    if (typeof parentPoint.selector !== 'string' || !parentPoint.selector) {
      throw new Error(`Missing or invalid parentInsertionPoint.selector for config '${configKey}'`);
    }
    if (typeof parentPoint.insertionType !== 'string' || !parentPoint.insertionType) {
      throw new Error(
        `Missing or invalid parentInsertionPoint.insertionType for config '${configKey}'`,
      );
    }

    if (!Object.values(DOMInsertionType).includes(parentPoint.insertionType as DOMInsertionType)) {
      throw new Error(
        `Invalid parentInsertionPoint.insertionType '${parentPoint.insertionType}' for config '${configKey}'. Valid types: ${Object.values(DOMInsertionType).join(', ')}`,
      );
    }

    if (parentPoint.selectionIndex != null && typeof parentPoint.selectionIndex !== 'number') {
      throw new Error(
        `Invalid parentInsertionPoint.selectionIndex for config '${configKey}'. Must be a number or null/undefined`,
      );
    }

    parentInsertionPoint = {
      selector: parentPoint.selector,
      insertionType: parentPoint.insertionType as DOMInsertionType,
      selectionIndex: parentPoint.selectionIndex as number | undefined,
    };
  }

  // Validate optional wrappingElement if present
  let wrappingElement;
  if (normalizedConfig.wrappingElement) {
    if (
      typeof normalizedConfig.wrappingElement !== 'object' ||
      Array.isArray(normalizedConfig.wrappingElement)
    ) {
      throw new Error(`Invalid wrappingElement for config '${configKey}'. Must be an object`);
    }

    const wrapping = normalizedConfig.wrappingElement as Record<string, unknown>;
    if (typeof wrapping.className !== 'string' || !wrapping.className) {
      throw new Error(`Missing or invalid wrappingElement.className for config '${configKey}'`);
    }
    if (typeof wrapping.element !== 'string' || !wrapping.element) {
      throw new Error(`Missing or invalid wrappingElement.element for config '${configKey}'`);
    }
    if (typeof wrapping.insertionType !== 'string' || !wrapping.insertionType) {
      throw new Error(`Missing or invalid wrappingElement.insertionType for config '${configKey}'`);
    }

    if (!Object.values(DOMInsertionType).includes(wrapping.insertionType as DOMInsertionType)) {
      throw new Error(
        `Invalid wrappingElement.insertionType '${wrapping.insertionType}' for config '${configKey}'. Valid types: ${Object.values(DOMInsertionType).join(', ')}`,
      );
    }

    wrappingElement = {
      className: wrapping.className,
      element: wrapping.element,
      insertionType: wrapping.insertionType as DOMInsertionType,
    };
  }

  // Validate optional fields
  if (
    normalizedConfig.divCheckSelector != null &&
    typeof normalizedConfig.divCheckSelector !== 'string'
  ) {
    throw new Error(
      `Invalid divCheckSelector for config '${configKey}'. Must be a string or null/undefined`,
    );
  }
  if (normalizedConfig.className != null && typeof normalizedConfig.className !== 'string') {
    throw new Error(
      `Invalid className for config '${configKey}'. Must be a string or null/undefined`,
    );
  }
  if (
    normalizedConfig.suppressionSelector != null &&
    typeof normalizedConfig.suppressionSelector !== 'string'
  ) {
    throw new Error(
      `Invalid suppressionSelector for config '${configKey}'. Must be a string or null/undefined`,
    );
  }
  if (
    normalizedConfig.dataAttributes != null &&
    (typeof normalizedConfig.dataAttributes !== 'object' ||
      Array.isArray(normalizedConfig.dataAttributes))
  ) {
    throw new Error(
      `Invalid dataAttributes for config '${configKey}'. Must be an object or null/undefined`,
    );
  }
  if (
    normalizedConfig.style != null &&
    (typeof normalizedConfig.style !== 'object' || Array.isArray(normalizedConfig.style))
  ) {
    throw new Error(`Invalid style for config '${configKey}'. Must be an object or null/undefined`);
  }

  // Validate gridInsertionType if present
  let gridInsertionType;
  if (normalizedConfig.gridInsertionType) {
    if (
      typeof normalizedConfig.gridInsertionType !== 'string' ||
      !normalizedConfig.gridInsertionType
    ) {
      throw new Error(`Invalid gridInsertionType for config '${configKey}'. Must be a string`);
    }

    if (
      !Object.values(GridInsertionType).includes(
        normalizedConfig.gridInsertionType as GridInsertionType,
      )
    ) {
      throw new Error(
        `Invalid gridInsertionType '${normalizedConfig.gridInsertionType}' for config '${configKey}'. Valid types: ${Object.values(GridInsertionType).join(', ')}`,
      );
    }
    gridInsertionType = normalizedConfig.gridInsertionType as GridInsertionType;
  }

  // Build the return object, only including optional fields if they have values
  const result: WidgetMountingConfig = {
    mountingConfigId: normalizedConfig.mountingConfigId,
    containerId: normalizedConfig.containerId,
    testId: normalizedConfig.testId,
    insertionPoint: {
      selector: insertionPoint.selector,
      insertionType: insertionPoint.insertionType as DOMInsertionType,
      ...(insertionPoint.selectionIndex != null && {
        selectionIndex: insertionPoint.selectionIndex,
      }),
    },
  };

  // Only add optional fields if they have values
  if (parentInsertionPoint) {
    result.parentInsertionPoint = parentInsertionPoint;
  }
  if (gridInsertionType) {
    result.gridInsertionType = gridInsertionType;
  }
  if (wrappingElement) {
    result.wrappingElement = wrappingElement;
  }
  if (normalizedConfig.divCheckSelector) {
    result.divCheckSelector = normalizedConfig.divCheckSelector as string;
  }
  if (normalizedConfig.className) {
    result.className = normalizedConfig.className as string;
  }
  if (normalizedConfig.suppressionSelector) {
    result.suppressionSelector = normalizedConfig.suppressionSelector;
  }
  if (normalizedConfig.dataAttributes) {
    result.dataAttributes = normalizedConfig.dataAttributes as Record<string, string>;
  }
  if (normalizedConfig.style) {
    result.style = normalizedConfig.style as Partial<CSSStyleDeclaration>;
  }

  return result;
};

// Helper function to validate and transform WidgetConfigQueryData to WidgetConfig
export const validateAndTransformWidgetConfig = (
  config: Record<string, unknown>,
  configKey: string,
): WidgetConfig => {
  const normalizedConfig = transformSnakeToCamel(config) as Record<string, unknown>;

  if (typeof normalizedConfig.widgetConfigId !== 'string' || !normalizedConfig.widgetConfigId) {
    throw new Error(`Missing or invalid widgetConfigId for widget config '${configKey}'`);
  }
  if (typeof normalizedConfig.type !== 'string' || !normalizedConfig.type) {
    throw new Error(`Missing or invalid type for widget config '${configKey}'`);
  }

  if (normalizedConfig.contentId != null && typeof normalizedConfig.contentId !== 'string') {
    throw new Error(
      `Invalid contentId for widget config '${configKey}'. Must be a string or null/undefined`,
    );
  }

  // Note: Widget configs are complex unions with type-specific properties.

  // Return the normalized config as WidgetConfig
  // Use intermediate 'unknown' cast for complex union types
  return normalizedConfig as unknown as WidgetConfig;
};

export const validateGraphQLFrontendConfig = (data: unknown): FrontendConfig | undefined => {
  if (data === null || data === undefined) {
    return undefined;
  }

  if (typeof data !== 'object' || Array.isArray(data)) {
    logger.logWarn('Invalid GraphQL frontend config data: not an object', undefined, { data });
    return undefined;
  }

  const obj = data as Record<string, unknown>;

  // Validate merchant_override_css if present
  if (
    'merchant_override_css' in obj &&
    typeof obj.merchant_override_css !== 'string' &&
    obj.merchant_override_css !== null
  ) {
    logger.logWarn(
      'Invalid GraphQL frontend config data: merchant_override_css must be a string or null',
      undefined,
      { data },
    );
    return undefined;
  }

  // Validate page_variants if present
  if ('page_variants' in obj) {
    if (
      obj.page_variants !== undefined &&
      obj.page_variants !== null &&
      !Array.isArray(obj.page_variants)
    ) {
      logger.logWarn(
        'Invalid GraphQL frontend config data: page_variants must be an array or null/undefined',
        undefined,
        { data },
      );
      return undefined;
    }
  }

  // Validate mounting_configs if present
  if ('mounting_configs' in obj) {
    if (
      obj.mounting_configs !== undefined &&
      obj.mounting_configs !== null &&
      !Array.isArray(obj.mounting_configs)
    ) {
      logger.logWarn(
        'Invalid GraphQL frontend config data: mounting_configs must be an array or null/undefined',
        undefined,
        { data },
      );
      return undefined;
    }
  }

  // Validate widget_configs if present
  if ('widget_configs' in obj) {
    if (
      obj.widget_configs !== undefined &&
      obj.widget_configs !== null &&
      !Array.isArray(obj.widget_configs)
    ) {
      logger.logWarn(
        'Invalid GraphQL frontend config data: widget_configs must be an array or null/undefined',
        undefined,
        { data },
      );
      return undefined;
    }
  }

  // Validate ui_configs if present
  if ('ui_configs' in obj && typeof obj.ui_configs !== 'object' && obj.ui_configs !== null) {
    logger.logWarn(
      'Invalid GraphQL frontend config data: ui_configs must be an object or null/undefined',
      undefined,
      { data },
    );
    return undefined;
  }

  try {
    // Transform the data to camelCase
    const transformedData = transformSnakeToCamel(obj) as Record<string, unknown>;

    // Convert page_variants from array to validated array if present
    if (transformedData.pageVariants && Array.isArray(transformedData.pageVariants)) {
      const pageVariantsArray = transformedData.pageVariants;
      const validatedPageVariants: PageVariantConfig[] = [];

      for (const variant of pageVariantsArray) {
        if (variant && typeof variant === 'object' && typeof variant.variantId === 'string') {
          try {
            const validatedVariant = validateAndTransformPageVariants(variant);
            validatedPageVariants.push(validatedVariant);
          } catch (error) {
            logger.logWarn('Invalid page variant, skipping', error, {
              variantId: variant.variantId,
              variant,
            });
            // Continue processing other variants
          }
        } else {
          logger.logWarn('Invalid page variant structure, skipping', undefined, { variant });
        }
      }

      transformedData.pageVariants = validatedPageVariants;
    }

    // Convert mounting_configs from array to Record if present
    if (transformedData.mountingConfigs && Array.isArray(transformedData.mountingConfigs)) {
      const mountingConfigsArray = transformedData.mountingConfigs as Array<{
        key: string;
        config: unknown;
      }>;
      const mountingConfigsRecord: Record<string, WidgetMountingConfig> = {};

      for (const item of mountingConfigsArray) {
        if (
          item &&
          typeof item.key === 'string' &&
          item.config &&
          typeof item.config === 'object'
        ) {
          try {
            // Validate and transform the config structure
            const config = item.config as Record<string, unknown>;
            const validatedConfig = validateAndTransformMountingConfig(config, item.key);
            mountingConfigsRecord[item.key] = validatedConfig;
          } catch (error) {
            logger.logWarn('Invalid mounting config, skipping', error, {
              configKey: item.key,
              config: item.config,
            });
            // Continue processing other configs
          }
        } else {
          logger.logWarn('Invalid mounting config item structure, skipping', undefined, { item });
        }
      }

      transformedData.mountingConfigs = mountingConfigsRecord;
    }

    // Convert widget_configs from array to Record if present
    if (transformedData.widgetConfigs && Array.isArray(transformedData.widgetConfigs)) {
      const widgetConfigsArray = transformedData.widgetConfigs as Array<{
        key: string;
        config: unknown;
      }>;
      const widgetConfigsRecord: Record<string, unknown> = {};

      for (const item of widgetConfigsArray) {
        if (
          item &&
          typeof item.key === 'string' &&
          item.config &&
          typeof item.config === 'object'
        ) {
          try {
            // Validate and transform widget config using the dedicated validation function
            const config = item.config as Record<string, unknown>;
            const validatedConfig = validateAndTransformWidgetConfig(config, item.key);
            widgetConfigsRecord[item.key] = validatedConfig;
          } catch (error) {
            logger.logWarn('Invalid widget config, skipping', error, {
              configKey: item.key,
              config: item.config,
            });
            // Continue processing other configs
          }
        } else {
          logger.logWarn('Invalid widget config item structure, skipping', undefined, { item });
        }
      }

      transformedData.widgetConfigs = widgetConfigsRecord;
    }

    return transformedData as FrontendConfig;
  } catch (error) {
    logger.logWarn('Failed to validate and transform GraphQL frontend config data', error, {
      data,
    });
    return undefined;
  }
};
