import React from 'react';
import { UserData } from '@aiostreams/core';
import {
  QUALITIES,
  RESOLUTIONS,
  SERVICE_DETAILS,
  DEFAULT_PRECACHE_SELECTOR,
  DEFAULT_SMART_DETECT_ATTRIBUTES,
} from '../../../core/src/utils/constants';
import { useStatus } from './status';

const USER_DATA_KEY = 'aiostreams-user-data';

export function applyMigrations(config: any): UserData {
  if (
    config &&
    config.addonPassword !== undefined &&
    config.accessToken === undefined
  ) {
    config.accessToken = config.addonPassword;
  }
  if (config && config.addonPassword !== undefined) {
    delete config.addonPassword;
  }
  if (
    config &&
    config.accessToken !== undefined &&
    config.accessKey === undefined
  ) {
    config.accessKey = config.accessToken;
  }
  if (config && config.accessToken !== undefined) {
    delete config.accessToken;
  }
  if (
    config.deduplicator &&
    typeof config.deduplicator.multiGroupBehaviour === 'string'
  ) {
    switch (config.deduplicator.multiGroupBehaviour as string) {
      case 'remove_uncached':
        config.deduplicator.multiGroupBehaviour = 'aggressive';
        break;
      case 'remove_uncached_same_service':
        config.deduplicator.multiGroupBehaviour = 'conservative';
        break;
      case 'remove_nothing':
        config.deduplicator.multiGroupBehaviour = 'keep_all';
        break;
    }
  }

  if (typeof config.digitalReleaseFilter === 'boolean') {
    const oldValue = config.digitalReleaseFilter;
    config.digitalReleaseFilter = {
      enabled: oldValue,
      tolerance: 0,
      requestTypes: ['movie', 'series', 'anime'],
      addons: [],
    };
  }
  if (config.titleMatching?.matchYear) {
    config.yearMatching = {
      enabled: true,
      tolerance: config.titleMatching.yearTolerance
        ? config.titleMatching.yearTolerance
        : 1,
      requestTypes: config.titleMatching.requestTypes ?? [],
      addons: config.titleMatching.addons ?? [],
    };
    delete config.titleMatching.matchYear;
  }

  if (Array.isArray(config.groups)) {
    config.groups = {
      enabled: config.disableGroups ? false : true,
      groupings: config.groups,
      behaviour: 'parallel',
    };
  }

  if (config.showStatistics || config.statisticsPosition) {
    config.statistics = {
      enabled: config.showStatistics ?? false,
      position: config.statisticsPosition ?? 'bottom',
      statsToShow: ['addon', 'filter', 'timing'],
      ...(config.statistics ?? {}),
    };
    delete config.showStatistics;
    delete config.statisticsPosition;
  }

  const migrateHOSBS = (
    type: 'preferred' | 'required' | 'excluded' | 'included'
  ) => {
    if (Array.isArray(config[type + 'Encodes'])) {
      config[type + 'Encodes'] = config[type + 'Encodes'].filter(
        (encode: string) => {
          if (encode === 'H-OU' || encode === 'H-SBS') {
            config[type + 'VisualTags'] = [
              ...(config[type + 'VisualTags'] ?? []),
              encode,
            ];
            return false;
          }
          return true;
        }
      );
    }
  };

  migrateHOSBS('preferred');
  migrateHOSBS('required');
  migrateHOSBS('excluded');
  migrateHOSBS('included');

  // migrate comparisons of queryType to 'anime' to 'anime.series' or 'anime.movie'
  const migrateAnimeQueryTypeInExpression = (expr?: string) => {
    if (typeof expr !== 'string') return expr as any;
    let updated = expr.replace(
      /queryType\s*==\s*(["'])anime\1/g,
      "(queryType == 'anime.series' or queryType == 'anime.movie')"
    );
    updated = updated.replace(
      /(["'])anime\1\s*==\s*queryType/g,
      "(queryType == 'anime.series' or queryType == 'anime.movie')"
    );
    updated = updated.replace(
      /queryType\s*!=\s*(["'])anime\1/g,
      "(queryType != 'anime.series' and queryType != 'anime.movie')"
    );
    updated = updated.replace(
      /(["'])anime\1\s*!=\s*queryType/g,
      "(queryType != 'anime.series' and queryType != 'anime.movie')"
    );
    return updated;
  };

  const expressionLists = [
    'excludedStreamExpressions',
    'requiredStreamExpressions',
    'includedStreamExpressions',
    'preferredStreamExpressions',
  ] as const;

  for (const key of expressionLists) {
    if (Array.isArray((config as any)[key])) {
      (config as any)[key] = (config as any)[key].map((expr: unknown) => {
        if (typeof expr === 'string') {
          return migrateAnimeQueryTypeInExpression(expr);
        }
        if (typeof expr === 'object' && expr !== null && 'expression' in expr) {
          return {
            ...(expr as any),
            expression: migrateAnimeQueryTypeInExpression(
              (expr as any).expression
            ),
          };
        }
        return expr;
      });
    }
  }

  if (config.dynamicAddonFetching?.condition) {
    config.dynamicAddonFetching.condition = migrateAnimeQueryTypeInExpression(
      config.dynamicAddonFetching.condition
    );
  }

  if (config.groups?.groupings) {
    config.groups.groupings = config.groups.groupings.map((group: any) => ({
      ...group,
      condition: migrateAnimeQueryTypeInExpression(group.condition),
    }));
  }

  if (
    config.rpdbUseRedirectApi !== undefined &&
    config.usePosterRedirectApi === undefined
  ) {
    config.usePosterRedirectApi = config.rpdbUseRedirectApi;
    delete config.rpdbUseRedirectApi;
  }

  // migrate 'rpdb' to 'usePosterService' in all catalog modifications
  if (Array.isArray(config.catalogModifications)) {
    for (const mod of config.catalogModifications) {
      if (mod.usePosterService === undefined && mod.rpdb === true) {
        mod.usePosterService = true;
      }
      delete mod.rpdb;
    }
  }

  // migrate alwaysPrecache to precacheCondition, then precacheCondition to precacheSelector
  if (config.precacheSelector === undefined && config.precacheNextEpisode) {
    // First handle the old precacheCondition field
    if (config.precacheCondition !== undefined) {
      // Convert condition to selector format
      config.precacheSelector = `${config.precacheCondition} ? uncached(streams) : []`;
    } else {
      // Handle even older alwaysPrecache field
      config.precacheSelector =
        config.alwaysPrecache === true
          ? 'true ? uncached(streams) : []'
          : DEFAULT_PRECACHE_SELECTOR;
    }
  }
  delete config.alwaysPrecache;
  delete config.precacheCondition;

  // migrate p2pWrap to serviceWrap
  if (config.p2pWrap !== undefined && config.serviceWrap === undefined) {
    config.serviceWrap = config.p2pWrap;
    delete config.p2pWrap;
  }

  // migrate stream expressions from string[] to {expression, enabled}[]
  const streamExpressionKeys = [
    'excludedStreamExpressions',
    'requiredStreamExpressions',
    'preferredStreamExpressions',
    'includedStreamExpressions',
  ] as const;
  for (const key of streamExpressionKeys) {
    if (
      Array.isArray(config[key]) &&
      config[key].some((expr: unknown) => typeof expr === 'string')
    ) {
      config[key] = config[key].map((expr: unknown) =>
        typeof expr === 'string' ? { expression: expr, enabled: true } : expr
      );
    }
  }

  // migrate forceToTop at addon level to pinPosition set to 'top'
  if (config.presets && Array.isArray(config.presets)) {
    config.presets = config.presets.map((preset: any) => {
      if (
        preset.options?.forceToTop === true &&
        preset.options.pinPosition === undefined
      ) {
        delete preset.options.forceToTop;
        return {
          ...preset,
          options: {
            ...preset.options,
            pinPosition: 'top',
          },
        };
      }
      return preset;
    });
  }

  if (config.formatter && config.formatter.definition) {
    config.formatter.definitions = {
      ...(config.formatter.definitions ?? {}),
      custom: config.formatter.definition,
    };
    delete config.formatter.definition;
  }

  return config;
}

export function removeInvalidPresetReferences(config: UserData) {
  // remove references to non-existent presets in options:
  const existingPresetIds = config.presets?.map((preset) => preset.instanceId);
  if (config.proxy) {
    config.proxy.proxiedAddons = config.proxy.proxiedAddons?.filter((addon) =>
      existingPresetIds?.includes(addon)
    );
  }
  if (config.yearMatching) {
    config.yearMatching.addons = config.yearMatching.addons?.filter((addon) =>
      existingPresetIds?.includes(addon)
    );
  }
  if (config.titleMatching) {
    config.titleMatching.addons = config.titleMatching.addons?.filter((addon) =>
      existingPresetIds?.includes(addon)
    );
  }
  if (config.seasonEpisodeMatching) {
    config.seasonEpisodeMatching.addons =
      config.seasonEpisodeMatching.addons?.filter((addon) =>
        existingPresetIds?.includes(addon)
      );
  }
  if (config.groups?.groupings) {
    config.groups.groupings = config.groups.groupings.map((group) => ({
      ...group,
      addons: group.addons?.filter((addon) =>
        existingPresetIds?.includes(addon)
      ),
    }));
  }
  if (config.serviceWrap?.presets) {
    config.serviceWrap.presets = config.serviceWrap.presets.filter((preset) =>
      existingPresetIds?.includes(preset)
    );
  }
  return config;
}
export const DefaultUserData: UserData = {
  services: Object.values(SERVICE_DETAILS).map((service) => ({
    id: service.id,
    enabled: false,
    credentials: {},
  })),
  presets: [],
  formatter: {
    id: 'gdrive',
  },
  preferredQualities: Object.values(QUALITIES),
  preferredResolutions: Object.values(RESOLUTIONS),
  excludedQualities: ['CAM', 'SCR', 'TS', 'TC'],
  excludedVisualTags: ['3D'],
  sortCriteria: {
    global: [
      {
        key: 'cached',
        direction: 'desc',
      },
      {
        key: 'library',
        direction: 'desc',
      },
      {
        key: 'resolution',
        direction: 'desc',
      },
      {
        key: 'quality',
        direction: 'desc',
      },
      {
        key: 'streamExpressionScore',
        direction: 'desc',
      },
      {
        key: 'regexPatterns',
        direction: 'desc',
      },
      {
        key: 'streamType',
        direction: 'desc',
      },
      {
        key: 'visualTag',
        direction: 'desc',
      },
      {
        key: 'audioTag',
        direction: 'desc',
      },
      {
        key: 'audioChannel',
        direction: 'desc',
      },
      {
        key: 'encode',
        direction: 'desc',
      },
      {
        key: 'language',
        direction: 'desc',
      },
      {
        key: 'subtitle',
        direction: 'desc',
      },
      {
        key: 'size',
        direction: 'desc',
      },
    ],
  },
  posterService: 'rpdb',
  deduplicator: {
    enabled: true,
    keys: ['filename', 'infoHash'],
    multiGroupBehaviour: 'aggressive',
    cached: 'single_result',
    uncached: 'per_service',
    p2p: 'single_result',
    http: 'disabled',
    live: 'disabled',
    youtube: 'disabled',
    external: 'disabled',
    smartDetectAttributes: DEFAULT_SMART_DETECT_ATTRIBUTES,
    smartDetectRounding: 10,
    libraryBehaviour: 'ignore',
  },
  autoPlay: {
    enabled: true,
    method: 'matchingFile',
    attributes: ['resolution', 'quality', 'releaseGroup'],
  },
  cacheAndPlay: {
    enabled: false,
    streamTypes: ['usenet'],
  },
  statistics: {
    enabled: false,
    position: 'bottom',
    statsToShow: ['addon', 'filter', 'timing'],
    showFilterStatsOnNoStreams: true,
  },
  digitalReleaseFilter: {
    enabled: false,
    tolerance: 0,
    requestTypes: [],
    addons: [],
    showInfoOnFilter: true,
  },
  ageRangeTypes: ['usenet'],
  seasonEpisodeMatching: {
    addons: [],
    requestTypes: [],
  },
  yearMatching: {
    addons: [],
    requestTypes: [],
  },
  titleMatching: {
    addons: [],
    requestTypes: [],
  },
  precacheNextEpisode: false,
  precacheSingleStream: true,
  precacheSelector: DEFAULT_PRECACHE_SELECTOR,
  enableSeadex: true,
  regexOverrides: [],
  checkOwned: true,
};

interface UserDataContextType {
  userData: UserData;
  setUserData: (data: ((prev: UserData) => UserData | null) | null) => void;
  uuid: string | null;
  setUuid: (uuid: string | null) => void;
  password: string | null;
  setPassword: (password: string | null) => void;
  encryptedPassword: string | null;
  setEncryptedPassword: (encryptedPassword: string | null) => void;
}

const UserDataContext = React.createContext<UserDataContextType | undefined>(
  undefined
);

export function UserDataProvider({ children }: { children: React.ReactNode }) {
  const { status } = useStatus();

  // Initialize userData from local storage or apply default
  const [userData, setUserData] = React.useState<UserData>(() => {
    try {
      const stored = localStorage.getItem(USER_DATA_KEY);
      const data = stored ? JSON.parse(stored) : DefaultUserData;
      return applyMigrations(data);
    } catch {
      return DefaultUserData;
    }
  });

  const [uuid, setUuid] = React.useState<string | null>(null);
  const [password, setPassword] = React.useState<string | null>(null);
  const [encryptedPassword, setEncryptedPassword] = React.useState<
    string | null
  >(null);

  // Effect to persist userData to local storage
  React.useEffect(() => {
    localStorage.setItem(USER_DATA_KEY, JSON.stringify(userData));
  }, [userData]);

  const statusApplied = React.useRef(false);

  React.useEffect(() => {
    if (!status || statusApplied.current) return;
    statusApplied.current = true;

    const forced = status.settings.forced;
    const defaults = status.settings.defaults;
    const services = status.settings.services;

    setUserData((prev) => {
      const newData = { ...prev };
      newData.proxy = {
        ...newData.proxy,
        enabled: forced.proxy.enabled ?? defaults.proxy?.enabled ?? undefined,
        id: (forced.proxy.id ?? defaults.proxy?.id ?? 'builtin') as
          | 'builtin'
          | 'mediaflow'
          | 'stremthru'
          | undefined,
        url: forced.proxy.url ?? defaults.proxy?.url ?? undefined,
        publicUrl:
          forced.proxy.publicUrl ?? defaults.proxy?.publicUrl ?? undefined,
        publicIp:
          forced.proxy.publicIp ?? defaults.proxy?.publicIp ?? undefined,
        credentials:
          forced.proxy.credentials ?? defaults.proxy?.credentials ?? undefined,
        proxiedServices:
          forced.proxy.proxiedServices ?? defaults.proxy?.proxiedServices ?? [],
      };

      newData.services = (newData.services ?? []).map((service) => {
        const serviceMeta = services[service.id];
        if (!serviceMeta) return service;
        serviceMeta.credentials.forEach((credential) => {
          if (credential.forced) {
            service.credentials[credential.id] = credential.forced;
          } else if (credential.default) {
            service.credentials[credential.id] = credential.default;
          }
        });
        // enable if every credential is set
        service.enabled = serviceMeta.credentials.every(
          (credential) =>
            credential.forced ||
            credential.default ||
            service.credentials[credential.id] !== undefined
        );
        return service;
      });

      return newData;
    });
  }, [status]);

  const safeSetUserData = (
    data: ((prev: UserData) => UserData | null) | null
  ) => {
    if (data === null) {
      setUserData(DefaultUserData);
    } else {
      setUserData((prev) => {
        const result = data(prev);
        return result === null ? DefaultUserData : result;
      });
    }
  };

  return (
    <UserDataContext.Provider
      value={{
        userData,
        setUserData: safeSetUserData,
        uuid,
        setUuid,
        password,
        setPassword,
        encryptedPassword,
        setEncryptedPassword,
      }}
    >
      {children}
    </UserDataContext.Provider>
  );
}

export function useUserData() {
  const context = React.useContext(UserDataContext);
  if (context === undefined) {
    throw new Error('useUserData must be used within a UserDataProvider');
  }
  return context;
}

export function useParentInheritance() {
  const { userData } = useUserData();
  const parentConfig = userData?.parentConfig;
  const strategies = parentConfig?.mergeStrategies;

  function isInherited(
    section:
      | 'presets'
      | 'services'
      | 'filters'
      | 'sorting'
      | 'formatter'
      | 'proxy'
      | 'metadata'
      | 'misc'
  ): boolean {
    if (!parentConfig) return false;
    const strategy = strategies?.[section] ?? 'inherit';
    return strategy === 'inherit';
  }

  return {
    hasParent: !!parentConfig,
    parentUuid: parentConfig?.uuid,
    isInherited,
  };
}
