import type { TypedDocumentNode } from '@graphql-typed-document-node/core'
import type { ApolloClient, NormalizedCacheObject } from '@apollo/client/core'

type Endpoint = 'user' | 'team' | 'public'
type Api = 'teams' | 'exchange' | 'kyc' | 'orders'

const RESOURCE_MAP: Record<Api, string> = {
  teams: 'https://teams.optovia.ru',
  orders: 'https://orders.optovia.ru',
  kyc: 'https://kyc.optovia.ru',
  exchange: 'https://exchange.optovia.ru',
}

export const useGraphQL = () => {
  const { $apollo } = useNuxtApp() as unknown as { $apollo: Record<string, ApolloClient<NormalizedCacheObject>> }
  const auth = useAuth()
  const { activeLogtoOrgId } = useActiveTeam()

  const getClient = (endpoint: Endpoint, api: Api): ApolloClient<NormalizedCacheObject> => {
    const clientMap: Record<string, ApolloClient<NormalizedCacheObject>> = {
      'public:exchange': $apollo.publicExchange!,
      'user:teams': $apollo.teamsUser!,
      'team:teams': $apollo.teamsTeam!,
      'team:exchange': $apollo.exchangeTeam!,
      'team:kyc': $apollo.kycTeam!,
      'team:orders': $apollo.ordersTeam!,
    }

    return clientMap[`${endpoint}:${api}`] || $apollo.publicExchange!
  }

  const getTokenType = (endpoint: Endpoint): 'id' | 'access' | 'none' => {
    if (endpoint === 'user') return 'id'
    if (endpoint === 'team') return 'access'
    return 'none'
  }

  const execute = async <TResult, TVariables extends Record<string, unknown>>(
    document: TypedDocumentNode<TResult, TVariables>,
    variables: TVariables,
    endpoint: Endpoint,
    api: Api
  ): Promise<TResult> => {
    const client = getClient(endpoint, api)
    const tokenType = getTokenType(endpoint)

    // Get token for context
    let token: string | null = null
    if (tokenType === 'id') {
      try {
        token = await auth.getIdToken() ?? null
      } catch (e) {
        console.warn('Failed to get ID token:', e)
      }
    } else if (tokenType === 'access') {
      try {
        token = await auth.getAccessToken(RESOURCE_MAP[api], activeLogtoOrgId.value || undefined)
      } catch (e) {
        console.warn('Failed to get access token:', e)
      }
    }

    if (tokenType !== 'none' && !token) {
      throw new Error('User not authenticated')
    }

    const context = token
      ? { headers: { Authorization: `Bearer ${token}` } }
      : {}

    const result = await client.query({
      query: document,
      variables,
      context,
      fetchPolicy: 'network-only',
    })

    if (result.errors?.length) {
      throw new Error(result.errors[0]?.message || 'GraphQL error')
    }

    return result.data as TResult
  }

  const mutate = async <TResult, TVariables extends Record<string, unknown>>(
    document: TypedDocumentNode<TResult, TVariables>,
    variables: TVariables,
    endpoint: Endpoint,
    api: Api
  ): Promise<TResult> => {
    const client = getClient(endpoint, api)
    const tokenType = getTokenType(endpoint)

    let token: string | null = null
    if (tokenType === 'id') {
      try {
        token = await auth.getIdToken() ?? null
      } catch (e) {
        console.warn('Failed to get ID token:', e)
      }
    } else if (tokenType === 'access') {
      try {
        token = await auth.getAccessToken(RESOURCE_MAP[api], activeLogtoOrgId.value || undefined)
      } catch (e) {
        console.warn('Failed to get access token:', e)
      }
    }

    if (tokenType !== 'none' && !token) {
      throw new Error('User not authenticated')
    }

    const context = token
      ? { headers: { Authorization: `Bearer ${token}` } }
      : {}

    const result = await client.mutate({
      mutation: document,
      variables,
      context,
    })

    if (result.errors?.length) {
      throw new Error(result.errors[0]?.message || 'GraphQL error')
    }

    return result.data as TResult
  }

  return { execute, mutate, getClient }
}
