import type { TypedDocumentNode } from '@graphql-typed-document-node/core'

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

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',
  geo: 'https://geo.optovia.ru',
  billing: 'https://billing.optovia.ru'
}

const CLIENT_MAP: Record<string, string> = {
  'public:exchange': 'default',
  'public:geo': 'publicGeo',
  'user:teams': 'teamsUser',
  'user:kyc': 'kycUser',
  'team:teams': 'teamsTeam',
  'team:exchange': 'exchangeTeam',
  'team:orders': 'ordersTeam',
  'team:billing': 'billingTeam'
}

export const useGraphQL = () => {
  const auth = useAuth()
  const { activeLogtoOrgId } = useActiveTeam()

  const getClientId = (endpoint: Endpoint, api: Api): string => {
    return CLIENT_MAP[`${endpoint}:${api}`] || 'default'
  }

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

  const getAuthContext = async (endpoint: Endpoint, api: Api) => {
    const tokenType = getTokenType(endpoint)

    if (tokenType === 'none') return {}

    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 (!token) {
      throw new Error('User not authenticated')
    }

    return { headers: { Authorization: `Bearer ${token}` } }
  }

  const execute = async <TResult, TVariables extends Record<string, unknown>>(
    document: TypedDocumentNode<TResult, TVariables>,
    variables: TVariables,
    endpoint: Endpoint,
    api: Api
  ): Promise<TResult> => {
    const clientId = getClientId(endpoint, api)
    const { client } = useApolloClient(clientId)
    const context = await getAuthContext(endpoint, api)

    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 clientId = getClientId(endpoint, api)
    const { client } = useApolloClient(clientId)
    const context = await getAuthContext(endpoint, api)

    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 }
}
