import { apolloClient } from '../../../shared/api/apolloClient'
import {
  clearAuthStorage,
  CURRENT_USER_STORAGE_KEY,
} from '../../../shared/api/authStorage'
import type {
  CreateUserPayload as ApiCreateUserPayload,
  SortDirection as ApiSortDirection,
  UpdateUserPayload as ApiUpdateUserPayload,
  UserFieldsFragment,
  UserRole as ApiUserRole,
  UserSortField as ApiUserSortField,
} from '../../../shared/api/graphql/generated/graphql'
import {
  CREATE_USER_MUTATION,
  DELETE_USER_MUTATION,
  GET_USER_QUERY,
  GET_USERS_QUERY,
  UPDATE_USER_MUTATION,
} from '../../../shared/api/graphql/operations/users'
import type {
  Employee,
  EmployeeRole,
  UserSortDirection,
  UserSortField,
} from '../model/types'

const LEGACY_USERS_STORAGE_KEY = 'fleet-ble-monitor.users'

export type UsersQueryParams = {
  page?: number
  pageSize?: number
  query?: string
  roles?: EmployeeRole[]
  sortDirection?: UserSortDirection
  sortField?: UserSortField
}

export type UsersPage = {
  totalPages: number
  totalElements: number
  page: Employee[]
}

export type CreateUserPayload = {
  lastName: string
  firstName: string
  middleName: string
  email: string
  login: string
  password: string
  role: EmployeeRole
  avatarUrl?: string | null
}

export type UpdateUserPayload = Partial<CreateUserPayload>

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

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

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

  window.localStorage.removeItem(LEGACY_USERS_STORAGE_KEY)
}

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

function normalizeRole(role: EmployeeRole): ApiUserRole | null {
  if (role === 'ADMIN' || role === 'Admin') return 'ADMIN'
  if (role === 'USER' || role === 'User') return 'USER'

  return null
}

function mapRoles(roles?: EmployeeRole[]): ApiUserRole[] | undefined {
  const normalizedRoles = roles
    ?.map(normalizeRole)
    .filter((role): role is ApiUserRole => Boolean(role))

  return normalizedRoles?.length ? normalizedRoles : undefined
}

function mapSortDirection(sortDirection: UserSortDirection): ApiSortDirection {
  return sortDirection === 'ASC' ? 'ASC' : 'DESC'
}

function mapSortField(sortField: UserSortField): ApiUserSortField {
  if (sortField === 'ID') return 'ID'
  if (sortField === 'Name') return 'NAME'

  return 'DATE'
}

function toCreateUserPayload(payload: CreateUserPayload): ApiCreateUserPayload {
  const role = normalizeRole(payload.role) ?? 'USER'

  return {
    lastName: payload.lastName.trim(),
    firstName: payload.firstName.trim(),
    middleName: payload.middleName.trim() || null,
    email: payload.email.trim(),
    login: payload.login.trim(),
    password: payload.password,
    role,
  }
}

function toUpdateUserPayload(payload: UpdateUserPayload): ApiUpdateUserPayload {
  const nextPayload: ApiUpdateUserPayload = {}

  if (payload.lastName !== undefined) {
    nextPayload.lastName = payload.lastName.trim()
  }

  if (payload.firstName !== undefined) {
    nextPayload.firstName = payload.firstName.trim()
  }

  if (payload.middleName !== undefined) {
    nextPayload.middleName = payload.middleName.trim() || null
  }

  if (payload.email !== undefined) {
    nextPayload.email = payload.email.trim()
  }

  if (payload.login !== undefined) {
    nextPayload.login = payload.login.trim()
  }

  if (payload.password !== undefined) {
    nextPayload.password = payload.password
  }

  if (payload.role !== undefined) {
    nextPayload.role = normalizeRole(payload.role)
  }

  return nextPayload
}

function logoutIfCurrentUserDeleted(deletedUserId: number) {
  if (typeof window === 'undefined') return

  const storedCurrentUser = window.localStorage.getItem(CURRENT_USER_STORAGE_KEY)

  if (!storedCurrentUser) return

  try {
    const currentUser = JSON.parse(storedCurrentUser) as Pick<Employee, 'id'>

    if (currentUser.id !== deletedUserId) return
  } catch {
    return
  }

  clearAuthStorage()
  void apolloClient.clearStore()
  window.dispatchEvent(new Event('auth:logout'))
  window.dispatchEvent(new Event('auth:change'))
}

export const usersService = {
  async getUsers({
    page = 0,
    pageSize = 10,
    query,
    roles,
    sortDirection = 'DESC',
    sortField = 'Date',
  }: UsersQueryParams = {}): Promise<UsersPage> {
    cleanupLegacyUsersStorage()

    const { data } = await apolloClient.query({
      query: GET_USERS_QUERY,
      variables: {
        page,
        pageSize,
        query: query?.trim() || undefined,
        roles: mapRoles(roles),
        sortDirection: mapSortDirection(sortDirection),
        sortField: mapSortField(sortField),
      },
      fetchPolicy: 'no-cache',
    })

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

    return {
      totalPages: data.getUsers.totalPages,
      totalElements: data.getUsers.totalElements,
      page: data.getUsers.page.map(mapUser),
    }
  },

  async getUserById(id: number | string): Promise<Employee | null> {
    cleanupLegacyUsersStorage()

    const { data } = await apolloClient.query({
      query: GET_USER_QUERY,
      variables: {
        id: Number(id),
      },
      fetchPolicy: 'no-cache',
    })

    if (!data) {
      throw new Error('Не удалось загрузить пользователя')
    }

    return data.getUser ? mapUser(data.getUser) : null
  },

  async findUserByLoginOrEmail(loginOrEmail: string): Promise<Employee | null> {
    const response = await usersService.getUsers({
      page: 0,
      pageSize: 1,
      query: loginOrEmail.trim(),
      sortDirection: 'ASC',
      sortField: 'ID',
    })

    const normalizedLoginOrEmail = loginOrEmail.trim().toLowerCase()

    return response.page.find((user) => {
      return (
        user.email?.trim().toLowerCase() === normalizedLoginOrEmail ||
        user.login?.trim().toLowerCase() === normalizedLoginOrEmail ||
        user.username?.trim().toLowerCase() === normalizedLoginOrEmail
      )
    }) ?? null
  },

  async createUser(payload: CreateUserPayload): Promise<Employee> {
    cleanupLegacyUsersStorage()

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

    if (!data?.createUser) {
      throw new Error('Не удалось создать пользователя')
    }

    emitUsersChange()

    return mapUser(data.createUser)
  },

  async updateUser(
    id: number | string,
    payload: UpdateUserPayload,
  ): Promise<Employee> {
    cleanupLegacyUsersStorage()

    const { data } = await apolloClient.mutate({
      mutation: UPDATE_USER_MUTATION,
      variables: {
        id: Number(id),
        payload: toUpdateUserPayload(payload),
      },
      fetchPolicy: 'no-cache',
    })

    if (!data?.updateUser) {
      throw new Error('Не удалось обновить пользователя')
    }

    emitUsersChange()

    return mapUser(data.updateUser)
  },

  async deleteUser(id: number | string): Promise<boolean> {
    cleanupLegacyUsersStorage()

    const numericId = Number(id)
    const { data } = await apolloClient.mutate({
      mutation: DELETE_USER_MUTATION,
      variables: {
        id: numericId,
      },
      fetchPolicy: 'no-cache',
    })

    const isDeleted = Boolean(data?.deleteUser)

    if (!isDeleted) {
      throw new Error('Пользователь не найден')
    }

    logoutIfCurrentUserDeleted(numericId)
    emitUsersChange()

    return true
  },
}
