import React, { ReactNode, createContext, useContext, useMemo } from 'react';
import { OrgConfigFeatureGate } from 'src/application/models/api/orgConfigResults';
import { FeatureGates } from 'src/application/models/featureGates';
import Logger from 'src/application/logging/logger';

const FEATURE_FLAGS_STORAGE_KEY = 'spiffy-feature-flags';

const logger = new Logger('featureFlagServiceContext');

export interface FullFlagValue {
  resolved: boolean;
  initial: boolean | undefined;
  query_param_override: boolean | undefined;
  window_param_override: boolean | undefined;
  stored_override: boolean | undefined;
}

// This is the class that was previously implicitly used or defined elsewhere
export class FeatureFlagService {
  private featureGates: OrgConfigFeatureGate[];

  constructor(featureGates: OrgConfigFeatureGate[]) {
    this.featureGates = featureGates;
  }

  static getQueryParamFeatureGateOverrides(featureGate: FeatureGates): boolean | undefined {
    const url = window.location?.href ?? '';
    if (url.includes('?')) {
      const urlObj = new URL(url);
      const params = new URLSearchParams(urlObj.search);
      const paramsObj = Object.fromEntries(params.entries());
      const value = paramsObj[featureGate];

      if (value != null) {
        return value === 'true';
      }
    }

    return undefined;
  }

  static getWindowFeatureGateOverrides(featureGate: FeatureGates): boolean | undefined {
    if (typeof window === 'undefined') {
      return undefined;
    }
    const windowOverride = window as Window & {
      _envive?: { featureOverrides?: Record<string, boolean> };
      _spiffy?: { featureOverrides?: Record<string, boolean> };
    };
    const featureOverrides =
      windowOverride?._envive?.featureOverrides ?? windowOverride?._spiffy?.featureOverrides;
    const value = featureOverrides?.[String(featureGate)];
    if (value != null) {
      return value;
    }
    return undefined;
  }

  static getStoredFeatureGateOverrides(featureGate: FeatureGates): boolean | undefined {
    if (typeof window === 'undefined' || typeof window.localStorage === 'undefined') {
      return undefined;
    }
    try {
      const featureFlags = window.localStorage.getItem(FEATURE_FLAGS_STORAGE_KEY);
      if (!featureFlags) {
        return undefined;
      }
      const featureFlagsObj = JSON.parse(featureFlags) as Record<string, boolean>;
      return featureFlagsObj[featureGate];
    } catch (err) {
      logger.logError('getStoredFeatureGateOverrides: error parsing feature flags', err);
      return undefined;
    }
  }

  static getFeatureFlagOverride(featureGate: FeatureGates): boolean | undefined {
    const queryOverride = FeatureFlagService.getQueryParamFeatureGateOverrides(featureGate);
    if (queryOverride != null) {
      logger.logDebug(
        `isFeatureGateEnabled featureGate:${featureGate} overridden by URL param. New value: ${queryOverride}`,
      );
      return queryOverride;
    }

    const windowOverride = FeatureFlagService.getWindowFeatureGateOverrides(featureGate);
    if (windowOverride != null) {
      logger.logDebug(
        `isFeatureGateEnabled featureGate:${featureGate} overridden by window param. New value: ${windowOverride}`,
      );
      return windowOverride;
    }

    const storedOverride = FeatureFlagService.getStoredFeatureGateOverrides(featureGate);
    if (storedOverride != null) {
      logger.logDebug(
        `isFeatureGateEnabled featureGate:${featureGate} overridden by stored value. New value: ${storedOverride}`,
      );
      return storedOverride;
    }

    return undefined;
  }

  isFeatureGateEnabled = (featureGate: FeatureGates): boolean => {
    const gateValue = this.featureGates.find(gate => gate.name === featureGate);

    const featureFlagOverride = FeatureFlagService.getFeatureFlagOverride(featureGate);
    if (featureFlagOverride !== undefined) {
      return featureFlagOverride;
    }

    if (gateValue == null || gateValue.value == null) {
      logger.logDebug(
        `isFeatureGateEnabled featureGate:${featureGate} value is undefined - returning false`,
      );
      return false;
    }
    return gateValue.value;
  };

  // Ensure that standard overrides work as expected
  isClientSessionEnabled = (): boolean =>
    this.isFeatureGateEnabled(FeatureGates.IsClientSessionEnabled);

  getFeatureFlags = (): Record<string, boolean> => {
    return Object.fromEntries(
      Object.values(FeatureGates).map((featureGate: FeatureGates) => [
        featureGate,
        this.isFeatureGateEnabled(featureGate),
      ]),
    );
  };

  getFullFlagValue = (featureGate: FeatureGates): FullFlagValue => {
    const gateValue = this.featureGates.find(gate => gate.name === featureGate);
    return {
      resolved: this.isFeatureGateEnabled(featureGate),
      initial: gateValue?.value,
      query_param_override: FeatureFlagService.getQueryParamFeatureGateOverrides(featureGate),
      window_param_override: FeatureFlagService.getWindowFeatureGateOverrides(featureGate),
      stored_override: FeatureFlagService.getStoredFeatureGateOverrides(featureGate),
    };
  };

  getFullFlagValues = (): Record<string, FullFlagValue> => {
    const featureGates = Object.values(FeatureGates).map((featureGate: FeatureGates) => [
      featureGate,
      this.getFullFlagValue(featureGate),
    ]);

    return featureGates.length > 0
      ? featureGates.reduce<Record<string, FullFlagValue>>((acc, entry) => {
          const [name, value] = entry as [FeatureGates, FullFlagValue];
          if (name && value != null) {
            return { ...acc, [`feature_gate.${name}`]: value };
          }
          return acc;
        }, {})
      : {};
  };

  static persistFeatureGateOverrides(): void {
    if (typeof window === 'undefined' || typeof window.localStorage === 'undefined') {
      return;
    }
    const overrides = Object.values(FeatureGates)
      .map(
        featureGate =>
          [featureGate, FeatureFlagService.getFeatureFlagOverride(featureGate)] as const,
      )
      .filter(([, value]) => value !== undefined)
      .reduce<Record<string, boolean>>((acc, [key, value]) => ({ ...acc, [key]: value! }), {});
    window.localStorage.setItem(FEATURE_FLAGS_STORAGE_KEY, JSON.stringify(overrides));
  }
}

export interface FeatureFlagContextType {
  featureFlagService: FeatureFlagService | undefined;
}

const FeatureFlagServiceContext = createContext<FeatureFlagContextType | undefined>(undefined);

interface FeatureFlagServiceProviderProps {
  featureFlagService: FeatureFlagService;
  children: ReactNode;
}

export const FeatureFlagServiceProvider: React.FC<FeatureFlagServiceProviderProps> = ({
  featureFlagService: inputService,
  children,
}) => {
  const contextValue = useMemo(
    () => ({
      featureFlagService: inputService,
    }),
    [inputService],
  );

  return (
    <FeatureFlagServiceContext.Provider value={contextValue}>
      {children}
    </FeatureFlagServiceContext.Provider>
  );
};

export const useFeatureFlagService = () => {
  const context = useContext(FeatureFlagServiceContext);
  if (context === undefined) {
    throw new Error('useFeatureFlagService must be used within a FeatureFlagServiceProvider');
  }
  return context;
};
