import { ApolloClient, InMemoryCache, createHttpLink, ApolloLink, Observable } from '@apollo/client/core';
import { onError } from '@apollo/client/link/error';
import { setContext } from '@apollo/client/link/context';
import { API_CONFIG, REQUEST_HEADERS } from '../config/constants';
import { authManager } from './auth';
import { bannedCountryLink } from './links/bannedCountryLink';

function generateUUID(): string {
  if (typeof crypto !== 'undefined' && crypto.randomUUID) {
    return crypto.randomUUID();
  }
  return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (c) => {
    const r = (Math.random() * 16) | 0;
    const v = c === 'x' ? r : (r & 0x3) | 0x8;
    return v.toString(16);
  });
}

const httpLink = createHttpLink({
  uri: API_CONFIG.GRAPHQL_ENDPOINT,
});

const authLink = setContext(async (operation, { headers }) => {
  let token: string;
  try {
    token = await authManager.getToken();
  } catch (err) {
    console.error('Failed to get auth token:', err);
    throw err;
  }

  return {
    headers: {
      ...headers,
      'Accept': REQUEST_HEADERS['Accept'],
      'Accept-Language': REQUEST_HEADERS['Accept-Language'],
      'Accept-Encoding': REQUEST_HEADERS['Accept-Encoding'],
      'Content-Type': REQUEST_HEADERS['Content-Type'],
      'Connection': REQUEST_HEADERS['Connection'],
      'User-Agent': REQUEST_HEADERS['User-Agent'],
      'Authorization': `Bearer ${token}`,
      'x-profile-id': authManager.getProfileId(),
      'x-device-os': REQUEST_HEADERS['x-device-os'],
      'x-app-version': REQUEST_HEADERS['x-app-version'],
      'x-os-version': REQUEST_HEADERS['x-os-version'],
      'x-transaction-id': generateUUID(),
      'x-event-analytics-id': authManager.getAnalyticsId(),
    },
  };
});

// Auto-retry on UNAUTHENTICATED errors: force-refresh token and replay the request
let isRefreshing = false;
let pendingRetries: Array<() => void> = [];

const errorLink = onError(({ graphQLErrors, operation, forward }) => {
  const isAuthError = graphQLErrors?.some(
    (e) => e.extensions?.code === 'UNAUTHENTICATED'
  );

  if (!isAuthError) return;

  if (isRefreshing) {
    // Another refresh is in progress — queue this request to retry after
    return new Observable((observer) => {
      pendingRetries.push(() => {
        const subscriber = forward(operation).subscribe(observer);
        return () => subscriber.unsubscribe();
      });
    });
  }

  isRefreshing = true;

  return new Observable((observer) => {
    authManager
      .forceRefresh()
      .then(async () => {
        // Update this operation's headers with the new token
        const token = await authManager.getToken();
        const oldHeaders = operation.getContext().headers;
        operation.setContext({
          headers: {
            ...oldHeaders,
            'Authorization': `Bearer ${token}`,
            'x-transaction-id': generateUUID(),
          },
        });

        isRefreshing = false;

        // Retry all queued requests
        pendingRetries.forEach((cb) => cb());
        pendingRetries = [];

        // Retry this request
        const subscriber = forward(operation).subscribe(observer);
        return () => subscriber.unsubscribe();
      })
      .catch((err) => {
        isRefreshing = false;
        pendingRetries = [];
        observer.error(err);
      });
  });
});

export const apolloClient = new ApolloClient({
  link: ApolloLink.from([bannedCountryLink, errorLink, authLink, httpLink]),
  cache: new InMemoryCache({
    typePolicies: {
      Profile: {
        keyFields: ['id'],
      },
      Chat: {
        keyFields: ['id'],
      },
      ChatSummary: {
        keyFields: ['id'],
      },
    },
  }),
  defaultOptions: {
    watchQuery: {
      fetchPolicy: 'cache-and-network',
    },
  },
});
