import { apolloClient } from '../../../shared/api/apolloClient'
import {
  clearAuthStorage,
  CURRENT_USER_STORAGE_KEY,
  getStoredAccessToken,
  getStoredRefreshToken,
  saveAuthTokens,
} from '../../../shared/api/authStorage'
import {
  CURRENT_USER_QUERY,
  LOGIN_MUTATION,
  LOGOUT_MUTATION,
  REFRESH_SESSION_MUTATION,
} from '../../../shared/api/graphql/operations/auth'
import type { Employee } from '../../../entities/employee/model/types'
import type {
  CurrentUserQuery,
  LoginMutation,
  RefreshSessionMutation,
} from '../../../shared/api/graphql/generated/graphql'

type AuthUser =
  | LoginMutation['login']['user']
  | RefreshSessionMutation['refreshSession']['user']
  | NonNullable<CurrentUserQuery['currentUser']>

function toCurrentUser(user: AuthUser): Employee {
  return {
    id: user.id,
    firstName: user.firstName,
    lastName: user.lastName,
    middleName: user.middleName ?? '',
    email: user.email,
    login: user.login,
    username: user.login,
    createdAt: user.createdAt,
    avatarUrl: null,
    orgId: 0,
    role: user.role,
    org: null,
  }
}

function saveCurrentUser(user: Employee) {
  if (typeof window === 'undefined') return

  window.localStorage.setItem(CURRENT_USER_STORAGE_KEY, JSON.stringify(user))
}

function emitAuthChange() {
  if (typeof window === 'undefined') return

  window.dispatchEvent(new Event('auth:change'))
}

function emitLogout() {
  if (typeof window === 'undefined') return

  window.dispatchEvent(new Event('auth:logout'))
}

async function refreshCurrentSession(): Promise<Employee | null> {
  const refreshToken = getStoredRefreshToken()

  if (!refreshToken) return null

  const { data } = await apolloClient.mutate({
    mutation: REFRESH_SESSION_MUTATION,
    variables: {
      payload: {
        refreshToken,
      },
    },
    fetchPolicy: 'no-cache',
  })

  if (!data?.refreshSession) return null

  saveAuthTokens(
    data.refreshSession.accessToken,
    data.refreshSession.refreshToken,
  )

  const user = toCurrentUser(data.refreshSession.user)
  saveCurrentUser(user)

  return user
}

export const authService = {
  async login(loginOrEmail: string, password: string): Promise<Employee> {
    const { data } = await apolloClient.mutate({
      mutation: LOGIN_MUTATION,
      variables: {
        payload: {
          loginOrEmail,
          password,
        },
      },
      fetchPolicy: 'no-cache',
    })

    if (!data?.login) {
      throw new Error('Не удалось войти')
    }

    saveAuthTokens(data.login.accessToken, data.login.refreshToken)

    const user = toCurrentUser(data.login.user)
    saveCurrentUser(user)
    emitAuthChange()

    return user
  },

  logout() {
    const accessToken = getStoredAccessToken()

    if (accessToken) {
      void apolloClient.mutate({
        mutation: LOGOUT_MUTATION,
        context: {
          headers: {
            Authorization: `Bearer ${accessToken}`,
          },
        },
        fetchPolicy: 'no-cache',
      }).catch(() => undefined)
    }

    clearAuthStorage()
    void apolloClient.clearStore()
    emitLogout()
    emitAuthChange()
  },

  async getCurrentUser(): Promise<Employee | null> {
    if (typeof window === 'undefined') return null

    if (!getStoredAccessToken() && !getStoredRefreshToken()) {
      clearAuthStorage()

      return null
    }

    try {
      const { data } = await apolloClient.query({
        query: CURRENT_USER_QUERY,
        fetchPolicy: 'no-cache',
      })

      if (data?.currentUser) {
        const user = toCurrentUser(data.currentUser)
        saveCurrentUser(user)

        return user
      }

      const refreshedUser = await refreshCurrentSession()

      if (refreshedUser) {
        emitAuthChange()

        return refreshedUser
      }

      clearAuthStorage()

      return null
    } catch {
      clearAuthStorage()
      emitLogout()

      return null
    }
  },

  isAdmin(user: Employee | null) {
    return user?.role === 'ADMIN' || user?.role === 'Admin'
  },
}
