import { createInstance } from '@amplitude/analytics-browser';
import { EventsDispatcher, SpiffyEvent } from 'src/events';
import { LocalStorageKeys } from 'src/contexts/localStorageContext';
import Logger from 'src/application/logging/logger';
import type {
  BrowserClient,
  EnrichmentPlugin,
  Event,
  ServerZoneType,
} from '@amplitude/analytics-types';
import { FeatureFlagService } from 'src/contexts/featureFlagServiceContext/featureFlagServiceContext';
import { WidgetTypeV3 } from 'src/contexts/typesV3';
import { ChatElementDisplayLocationV3 } from 'src/application/models/chatElementDisplayLocationV3';
import { projectToGA4 } from '../ga4ProjectionService/ga4ProjectionService';
import { EnviveMetricsEventName, SpiffyMetricsEventName } from './eventNames';

// Re-export event names for convenience
export { EnviveMetricsEventName, SpiffyMetricsEventName };

const logger = new Logger('amplitudeService');

export interface TrackEventParams {
  eventName: SpiffyMetricsEventName | EnviveMetricsEventName;
  eventProps?: Record<string, unknown>;
  eventGroups?: Record<string, unknown>;
  alsoSendToGoogleAnalytics?: boolean;
}

export interface AmplitudeServiceConfig {
  userId: string;
  amplitudeApiKey: string;
  dataResidency: string;
  env: string;
  contextSource: string;
  orgShortName: string;
  orgId: string;
  featureFlagService: FeatureFlagService;
  orgGaConfig?: unknown;
  show: boolean;
  enviveOn: boolean;
  enabledFeatures?: Record<string, boolean>;
  getLocalStorageItem: null | ((key: string) => string | null);
}

export class AmplitudeService {
  private amplitudeClient: BrowserClient | undefined;

  private internalEventTrackingEnrichment: EnrichmentPlugin | undefined;

  private supplementalDefaultProps: Record<string, unknown> = {};

  private config: AmplitudeServiceConfig;

  constructor(config: AmplitudeServiceConfig) {
    this.config = config;
    this.initialize();
  }

  get isMockApiKey(): boolean {
    return this.config.amplitudeApiKey === 'mock-amplitude-key';
  }

  get isReady(): boolean {
    return Boolean(
      this.config.userId &&
      this.config.featureFlagService &&
      this.config.amplitudeApiKey &&
      this.amplitudeClient,
    );
  }

  private getLocalStorageItem(key: string): string | null {
    if (this.config.getLocalStorageItem) {
      return this.config.getLocalStorageItem(key);
    }
    if (typeof window !== 'undefined' && window.localStorage) {
      return window.localStorage.getItem(key);
    }
    return null;
  }

  private getDefaultTrackingProps(
    eventName: SpiffyMetricsEventName | EnviveMetricsEventName,
  ): Record<string, unknown> {
    const featureGates = Object.entries(this.config.featureFlagService.getFeatureFlags());
    const gatesProps =
      featureGates.length > 0
        ? featureGates.reduce<Record<string, boolean>>((acc, [name, value]) => {
            if (name && value != null) {
              return { ...acc, [`feature_gate.${name}`]: value };
            }
            return acc;
          }, {})
        : {};
    const experimentProps = {}; // No direct equivalent for experiments in EnviveConfig yet

    const environmentProps = {
      'environment.execution_context': 'bundle',
      'environment.page_url': window.location.href,
      'environment.envive_user_id': this.config.userId,
      'environment.execution_environment': this.config.env || 'unknown',
      'environment.context_source': this.config.contextSource,
      'environment.sales_agent_enabled': this.config.enabledFeatures?.salesAgent ?? false,
      'environment.search_enabled': this.config.enabledFeatures?.searchAgent ?? false,
      'environment.envive_on_query_param': this.config.enviveOn,
      'environment.window_show': this.config.show,
      'environment.envive_enabled': this.config.enabledFeatures?.envive,
    };

    const orgLevelAmplitudeTrackingProps = AmplitudeService.isV3EnviveEvent(eventName)
      ? {
          ...this.config.featureFlagService.getFullFlagValues(),
          ...experimentProps,
          ...environmentProps,
        }
      : {
          ...gatesProps,
          ...experimentProps,
        };
    return {
      ...orgLevelAmplitudeTrackingProps,
      ...this.supplementalDefaultProps,
      app_id: 'commerce-chat-react-component',
      chat_id: this.getLocalStorageItem(LocalStorageKeys.ChatId),
      env: this.config.env || 'unknown',
      app_source: this.config.contextSource,
      org_id: this.config.orgId,
      'org.short_name': this.config.orgShortName,
      'user.id': this.config.userId,
      'cdp.user_id': null,
      'cdp.provider': null,
      'event.source': 'web-browser',
      'event.type': 'user-activity',
      'event.id': null,
      'event.channel': 'web',
      'event.timestamp': null,
    };
  }

  // eslint-disable-next-line class-methods-use-this
  private eventPropsToPrefixedEventProps(
    eventName: SpiffyMetricsEventName | EnviveMetricsEventName,
    eventProps: Record<string, unknown>,
  ): Record<string, unknown> {
    const prefix = eventName.toLowerCase().replace(/\s+/g, '_');
    return Object.entries(eventProps).reduce(
      (acc, [key, value]) => {
        acc[`${prefix}.${key}`] = value;
        return acc;
      },
      {} as Record<string, unknown>,
    );
  }

  private getEventTrackingEnrichment(): EnrichmentPlugin {
    if (this.internalEventTrackingEnrichment !== undefined) {
      return this.internalEventTrackingEnrichment;
    }

    const enrichment: EnrichmentPlugin = {
      name: 'page-view-tracking-enrichment',
      type: 'enrichment',
      setup: async () => undefined,
      execute: async (event: Event): Promise<Event> => {
        let enrichedEvent: Event;

        const eventsToEnrich = [
          '[Amplitude] Page Viewed',
          `[Spiffy] ${SpiffyMetricsEventName.BundleLoaded}`,
        ];

        if (eventsToEnrich.includes(event.event_type)) {
          const globalProperties: Record<string, string> = {};

          if (this.config.show != null) {
            globalProperties['globalProperties.show'] = String(this.config.show);
          }
          globalProperties['globalProperties.env'] = String(this.config.env);

          const enabledFeaturesProperties = Object.entries(
            this.config?.enabledFeatures ?? ({} as Record<string, boolean>),
          ).reduce<Record<string, string>>(
            (acc, [key, value]) => ({
              ...acc,
              [`enabledFeatures.${key}`]: `${value}`,
            }),
            {},
          );

          const timingProperties = {
            'timing.enriched_at_ms':
              typeof window !== 'undefined' ? window.performance?.now() : undefined,
          };

          enrichedEvent = {
            ...event,
            event_properties: {
              ...event.event_properties,
              ...this.getDefaultTrackingProps(
                event.event_type as SpiffyMetricsEventName | EnviveMetricsEventName,
              ),
              ...globalProperties,
              ...enabledFeaturesProperties,
              ...timingProperties,
            },
          };
        } else {
          enrichedEvent = event;
        }

        EventsDispatcher.dispatch(SpiffyEvent.AMPLITUDE_EVENT, enrichedEvent);

        return enrichedEvent;
      },
    };
    this.internalEventTrackingEnrichment = enrichment;
    return enrichment;
  }

  private initialize(): void {
    const isReady = Boolean(
      this.config.userId && this.config.featureFlagService && this.config.amplitudeApiKey,
    );

    if (!isReady) {
      logger.logDebug('AmplitudeService is not ready', {
        isReady,
        userId: this.config.userId,
        hasFeatureFlagService: !!this.config.featureFlagService,
        hasAmplitudeApiKey: !!this.config.amplitudeApiKey,
      });
      return;
    }

    if (this.isMockApiKey) {
      logger.logWarn(
        'Mock API key detected — running in mock mode, no events will be sent to Amplitude.',
        this.config.amplitudeApiKey,
      );
      return;
    }

    if (!this.amplitudeClient) {
      const currentAmplitudeInstance: BrowserClient = createInstance();

      currentAmplitudeInstance.add(this.getEventTrackingEnrichment());
      currentAmplitudeInstance.init(this.config.amplitudeApiKey, this.config.userId, {
        serverZone: this.config.dataResidency as ServerZoneType,
        trackingOptions: {
          ipAddress: true,
        },
        autocapture: {
          attribution: true,
          pageViews: {
            trackHistoryChanges: 'pathOnly',
          },
          sessions: false,
          formInteractions: false,
          fileDownloads: false,
        },
      });
      this.amplitudeClient = currentAmplitudeInstance;
    }
  }

  static mapWidgetTypeToChatComponent(widgetType: unknown): string {
    switch (widgetType) {
      case WidgetTypeV3.FloatingButtonV3:
        return 'floating_button';
      case WidgetTypeV3.ChatPreviewV3:
      case WidgetTypeV3.SocialProofV3:
        return 'embedded_widget';
      case WidgetTypeV3.TitledPromptCarouselV3:
      case WidgetTypeV3.PromptCarouselV3:
        return 'suggestion_bar';
      case WidgetTypeV3.ProductCardV3:
        return 'image_prompt_card';
      default:
        return 'unknown';
    }
  }

  static mapTriggerLocationToChatComponent(triggerLocation: unknown): string {
    switch (triggerLocation) {
      case ChatElementDisplayLocationV3.PRODUCT_CARD_PROMPT_BUTTON:
      case ChatElementDisplayLocationV3.PRODUCT_CARD_TEXT_FIELD:
        return 'prompt_card';
      case ChatElementDisplayLocationV3.PROMPT_CAROUSEL:
      case ChatElementDisplayLocationV3.TITLED_PROMPT_CAROUSEL:
        return 'top_reviews_snippet';
      case ChatElementDisplayLocationV3.TYPING_ANIMATION:
      case ChatElementDisplayLocationV3.SOCIAL_PROOF_PRIMARY_BUTTON:
      case ChatElementDisplayLocationV3.SOCIAL_PROOF_SECONDARY_BUTTON:
      case ChatElementDisplayLocationV3.SOCIAL_PROOF_TEXT_FIELD:
      case ChatElementDisplayLocationV3.CHAT_PREVIEW_COMPARISON_PROMPT_BUTTON:
      case ChatElementDisplayLocationV3.CHAT_PREVIEW_COMPARISON_TEXT_FIELD:
      case ChatElementDisplayLocationV3.CHAT_PREVIEW_PROMPT_BUTTON:
      case ChatElementDisplayLocationV3.CHAT_PREVIEW_TEXT_FIELD:
        return 'chat_preview';
      case ChatElementDisplayLocationV3.FLOATING_CHAT_OVERLAY:
      case ChatElementDisplayLocationV3.FLOATING_CHAT_CLOSE_BUTTON:
        return 'in_chat';
      default:
        return triggerLocation as string;
    }
  }

  // This allows us to map the event props to the correct format for the legacy amplitude events
  static mapEventProps({ eventName, eventProps }: TrackEventParams): Record<string, unknown> {
    const messageMetadata = eventProps?.message_metadata ?? {};
    const triggerLocation =
      messageMetadata !== null &&
      typeof messageMetadata === 'object' &&
      'trigger_location' in messageMetadata
        ? messageMetadata.trigger_location
        : undefined;

    if (eventName === SpiffyMetricsEventName.ChatComponentExpanded) {
      const mappedTriggerLoccation =
        AmplitudeService.mapTriggerLocationToChatComponent(triggerLocation);
      return {
        ...eventProps,
        message_metadata: {
          ...messageMetadata,
          trigger_location: mappedTriggerLoccation,
          full_trigger_location: triggerLocation,
        },
      };
    }

    if (eventName === SpiffyMetricsEventName.ChatComponentVisible) {
      const mappedWidgetType = AmplitudeService.mapWidgetTypeToChatComponent(
        eventProps?.widget_type,
      );
      const mappedTriggerLoccation =
        AmplitudeService.mapTriggerLocationToChatComponent(triggerLocation);
      return {
        ...eventProps,
        message_metadata: {
          ...messageMetadata,
          trigger_location: mappedTriggerLoccation,
          full_trigger_location: triggerLocation,
        },
        chat_component: mappedWidgetType,
        'chat.component_visible': true,
      };
    }
    return eventProps ?? {};
  }

  static isV3EnviveEvent(eventName: SpiffyMetricsEventName | EnviveMetricsEventName): boolean {
    return Object.values(EnviveMetricsEventName).includes(eventName as EnviveMetricsEventName);
  }

  static decorateEventName(eventName: SpiffyMetricsEventName | EnviveMetricsEventName): string {
    if (AmplitudeService.isV3EnviveEvent(eventName)) {
      return `[Envive] ${eventName}`;
    }
    return `[Spiffy] ${eventName}`;
  }

  async trackEvent({
    eventName,
    eventProps,
    eventGroups,
    alsoSendToGoogleAnalytics = false,
  }: TrackEventParams): Promise<void> {
    if (this.isMockApiKey) return;
    logger.logDebug('Submitting event', eventName);
    try {
      const decoratedEventName = AmplitudeService.decorateEventName(eventName);

      if (!this.amplitudeClient) {
        logger.logWarn('amplitude client undefined', undefined, {
          event_name: decoratedEventName,
        });
        return;
      }

      const mappedEventProps = AmplitudeService.mapEventProps({ eventName, eventProps });

      const eventData = JSON.stringify({
        eventName,
        eventProps: mappedEventProps,
        created_at: new Date().toISOString(),
      });
      const encoder = new TextEncoder();
      const data = encoder.encode(eventData);
      // calculate a hash of the event properties to use as the insert_id so that duplicate events
      // are automatically dropped by Amplitude
      const hashBuffer = await crypto.subtle.digest('SHA-256', data);
      const hashArray = Array.from(new Uint8Array(hashBuffer));
      const currentInsertId = hashArray.map(b => b.toString(16).padStart(2, '0')).join('');

      logger.logDebug(`amplitude tracking ${decoratedEventName}`, null, {
        event_name: decoratedEventName,
        props: mappedEventProps,
      });

      this.amplitudeClient.track(
        decoratedEventName,
        {
          ...this.getDefaultTrackingProps(eventName),
          ...mappedEventProps,
          ...(mappedEventProps
            ? this.eventPropsToPrefixedEventProps(eventName, mappedEventProps)
            : {}),
        },
        {
          ...eventGroups,
          insert_id: currentInsertId,
        },
      );

      if (AmplitudeService.isV3EnviveEvent(eventName) && this.config.orgGaConfig) {
        const mergedPropsForGA4 = {
          ...this.getDefaultTrackingProps(eventName),
          ...mappedEventProps,
        };
        projectToGA4(eventName as EnviveMetricsEventName, mergedPropsForGA4);
      } else if (alsoSendToGoogleAnalytics && this.config.orgGaConfig) {
        logger.logDebug('GA tracking', decoratedEventName);
        if (typeof window !== 'undefined' && window.dataLayer) {
          // eslint-disable-next-line @typescript-eslint/no-explicit-any
          (window.dataLayer as any[]).push({
            event: decoratedEventName,
            eventProps,
          });
        }
      }
    } catch (err) {
      logger.logError('Error tracking event', err, {
        eventName,
        eventProps,
      });
    }
  }

  // Ensure that supplemental default props are merged with the existing props
  setSupplementalDefaultProps(props: Record<string, unknown>): void {
    this.supplementalDefaultProps = props;
  }
}
