import {
    useCallback,
    useEffect,
    useMemo,
    useRef,
    useState,
} from 'react'
import { useSearchParams } from 'react-router-dom'
import { m } from 'framer-motion'

import type {
    Employee,
    EmployeeRole,
} from '../../../../entities/employee/model/types'
import {
    usersService,
    type UsersPage,
    type UsersQueryParams,
} from '../../../../entities/employee/api/usersService'
import {
    EmployeesToolbar,
    usersSortOptions,
} from '../EmployeesToolbar/EmployeesToolbar'
import { EmployeesPagination } from '../EmployeesPagination/EmployeesPagination'
import { UsersTable } from '../UsersTable/UsersTable'
import { ConfirmDangerDialog } from '../../../../widgets/ConfirmDangerDialog/ConfirmDangerDialog'
import { getPaginationItems } from '../../lib/getPaginationItems'
import { getEmployeeFullName } from '../../lib/employeeFormatters'
import {
    TableSkeleton,
    type TableSkeletonColumn,
} from '../../../../shared/ui/TableSkeleton/TableSkeleton'
import { RevealContent } from '../../../../shared/ui/RevealContent/RevealContent'
import { pageSwapVariants, uiFadeTransition } from '../../../../shared/lib/motion'
import { EmptyState } from '../../../../shared/ui/EmptyState/EmptyState'
import {
    useDeferredModalPayload,
} from '../../../../shared/lib/lazyMount'
import { AddEmployeeModal } from '../AddEmployeeModal/AddEmployeeModal'
import { FleetReportScheduleModal } from '../FleetReportScheduleModal/FleetReportScheduleModal'
import { authService } from '../../../../features/auth/api/authService'
import {
    Notification,
    type NotificationVariant,
} from '../../../../widgets/Notification/Notification'
import type { FleetReportSchedule } from '../../../../entities/report/model/types'

import SimpleBar from 'simplebar-react'
import 'simplebar-react/dist/simplebar.min.css'

const USERS_SCROLL_KEY = 'employees.users.scrollTop'

const usersTableSkeletonColumns: TableSkeletonColumn[] = [
    { variant: 'person', width: '70%', headerWidth: '54px' },
    { width: '72%', headerWidth: '118px' },
    { variant: 'pill', headerWidth: '54px' },
    { width: '62%', headerWidth: '94px' },
    { variant: 'actions', headerWidth: '132px' },
]

type UsersNotification = {
    id: number
    variant: NotificationVariant
    title: string
    description?: string
} | null

function getRoleFilter(value: string | null): EmployeeRole | null {
    if (value === 'ADMIN' || value === 'USER') return value

    return null
}

export function UsersSection() {
    const [searchParams, setSearchParams] = useSearchParams()
    const tableCardRef = useRef<HTMLDivElement | null>(null)
    const requestIdRef = useRef(0)

    const usersCurrentPage = Number(searchParams.get('uPage') ?? 0)
    const userSearch = searchParams.get('uQuery') ?? ''
    const roleFilter = getRoleFilter(searchParams.get('uRole'))

    const userSort = useMemo(() => {
        return usersSortOptions.find(
            (option) => option.value === searchParams.get('uSort'),
        ) ?? usersSortOptions[0]
    }, [searchParams])

    const [debouncedUserSearch, setDebouncedUserSearch] = useState('')
    const [usersResponse, setUsersResponse] = useState<UsersPage | null>(null)
    const [usersLoading, setUsersLoading] = useState(false)
    const [usersError, setUsersError] = useState<Error | null>(null)
    const [currentUser, setCurrentUser] = useState<Employee | null>(null)

    const [isAddEmployeeOpen, setIsAddEmployeeOpen] = useState(false)
    const [isReportScheduleOpen, setIsReportScheduleOpen] = useState(false)
    const [editingEmployee, setEditingEmployee] = useState<Employee | null>(null)
    const [deletingEmployee, setDeletingEmployee] = useState<Employee | null>(null)
    const [isDeletingEmployee, setIsDeletingEmployee] = useState(false)
    const [notification, setNotification] = useState<UsersNotification>(null)
    const renderEditingEmployee = useDeferredModalPayload(editingEmployee)
    const renderDeletingEmployee = useDeferredModalPayload(deletingEmployee)

    const canManageUsers = authService.isAdmin(currentUser)

    useEffect(() => {
        const timeoutId = window.setTimeout(() => {
            setDebouncedUserSearch(userSearch.trim())
        }, 350)

        return () => {
            window.clearTimeout(timeoutId)
        }
    }, [userSearch])

    useEffect(() => {
        let isMounted = true

        async function syncCurrentUser() {
            const user = await authService.getCurrentUser()

            if (isMounted) {
                setCurrentUser(user)
            }
        }

        syncCurrentUser()

        window.addEventListener('auth:change', syncCurrentUser)
        window.addEventListener('users:change', syncCurrentUser)

        return () => {
            isMounted = false
            window.removeEventListener('auth:change', syncCurrentUser)
            window.removeEventListener('users:change', syncCurrentUser)
        }
    }, [])

    const usersQueryVariables = useMemo<UsersQueryParams>(() => ({
        page: usersCurrentPage,
        query: debouncedUserSearch || undefined,
        roles: roleFilter ? [roleFilter] : undefined,
        sortDirection: userSort.sortDirection,
        sortField: userSort.sortField,
    }), [
        debouncedUserSearch,
        roleFilter,
        userSort.sortDirection,
        userSort.sortField,
        usersCurrentPage,
    ])

    const fetchUsers = useCallback(async (
        variables: UsersQueryParams = usersQueryVariables,
    ) => {
        const requestId = requestIdRef.current + 1
        requestIdRef.current = requestId

        setUsersLoading(true)
        setUsersError(null)

        try {
            const response = await usersService.getUsers(variables)

            if (requestIdRef.current === requestId) {
                setUsersResponse(response)
            }

            return response
        } catch (error) {
            if (requestIdRef.current === requestId) {
                setUsersError(error instanceof Error ? error : new Error('Не удалось загрузить пользователей'))
            }

            throw error
        } finally {
            if (requestIdRef.current === requestId) {
                setUsersLoading(false)
            }
        }
    }, [usersQueryVariables])

    useEffect(() => {
        fetchUsers().catch(() => undefined)
    }, [fetchUsers])

    const employees = usersResponse?.page ?? []
    const usersTotalPages = usersResponse?.totalPages ?? 0

    const isUsersInitialLoading = usersLoading && !usersResponse
    const isUsersUpdating = usersLoading && Boolean(usersResponse)
    const shouldShowUsersError = Boolean(usersError) && employees.length === 0
    const hasAppliedUsersFilters = Boolean(
        debouncedUserSearch || roleFilter,
    )

    const hasPrevUsersPage = usersCurrentPage > 0
    const hasNextUsersPage =
        usersTotalPages > 0 && usersCurrentPage < usersTotalPages - 1

    const usersPaginationItems = useMemo(
        () => getPaginationItems(usersCurrentPage, usersTotalPages),
        [usersCurrentPage, usersTotalPages],
    )

    useEffect(() => {
        if (isUsersInitialLoading) return

        const element = tableCardRef.current

        if (!element) return

        const savedScrollTop = Number(
            sessionStorage.getItem(USERS_SCROLL_KEY) ?? 0,
        )

        const frameId = window.requestAnimationFrame(() => {
            element.scrollTop = savedScrollTop
        })

        return () => {
            window.cancelAnimationFrame(frameId)
        }
    }, [isUsersInitialLoading, usersCurrentPage])

    const updateUsersSearchParams = useCallback((
        updates: Record<string, string | null>,
    ) => {
        setSearchParams(
            (prev) => {
                const next = new URLSearchParams(prev)

                Object.entries(updates).forEach(([key, value]) => {
                    if (value === null || value === '') {
                        next.delete(key)
                        return
                    }

                    next.set(key, value)
                })

                return next
            },
            {
                replace: true,
            },
        )
    }, [setSearchParams])

    const resetUsersScroll = useCallback(() => {
        sessionStorage.setItem(USERS_SCROLL_KEY, '0')
    }, [])

    const handleUsersScroll = useCallback(() => {
        const element = tableCardRef.current

        if (!element) return

        sessionStorage.setItem(USERS_SCROLL_KEY, String(element.scrollTop))
    }, [])

    const tableScrollProps = useMemo(
        () => ({
            ref: tableCardRef,
            onScroll: handleUsersScroll,
        }),
        [handleUsersScroll],
    )

    const setUsersPage = useCallback((page: number) => {
        resetUsersScroll()

        updateUsersSearchParams({
            uPage: page > 0 ? String(page) : null,
        })
    }, [resetUsersScroll, updateUsersSearchParams])

    const handleNextUsersPage = useCallback(() => {
        if (!hasNextUsersPage || usersLoading) return

        setUsersPage(usersCurrentPage + 1)
    }, [hasNextUsersPage, setUsersPage, usersCurrentPage, usersLoading])

    const handlePrevUsersPage = useCallback(() => {
        if (!hasPrevUsersPage || usersLoading) return

        setUsersPage(Math.max(0, usersCurrentPage - 1))
    }, [hasPrevUsersPage, setUsersPage, usersCurrentPage, usersLoading])

    const handleJumpPrevUsersPage = useCallback(() => {
        if (usersLoading) return

        setUsersPage(Math.max(0, usersCurrentPage - 5))
    }, [setUsersPage, usersCurrentPage, usersLoading])

    const handleJumpNextUsersPage = useCallback(() => {
        if (usersLoading) return

        setUsersPage(Math.min(usersTotalPages - 1, usersCurrentPage + 5))
    }, [setUsersPage, usersCurrentPage, usersLoading, usersTotalPages])

    const refetchFirstUsersPage = useCallback(async () => {
        setUsersPage(0)

        await fetchUsers({
            ...usersQueryVariables,
            page: 0,
        })
    }, [
        fetchUsers,
        setUsersPage,
        usersQueryVariables,
    ])

    const handleEditEmployee = useCallback((employee: Employee) => {
        if (!canManageUsers) return

        setEditingEmployee(employee)
    }, [canManageUsers])

    const handleDeleteEmployee = useCallback((employee: Employee) => {
        if (!canManageUsers) return

        setDeletingEmployee(employee)
    }, [canManageUsers])

    const handleConfirmDeleteEmployee = useCallback(async () => {
        if (!deletingEmployee || !canManageUsers) return

        try {
            setIsDeletingEmployee(true)
            await usersService.deleteUser(deletingEmployee.id)
            setDeletingEmployee(null)
            await refetchFirstUsersPage()
        } finally {
            setIsDeletingEmployee(false)
        }
    }, [canManageUsers, deletingEmployee, refetchFirstUsersPage])

    const handleUserSearchChange = useCallback((value: string) => {
        resetUsersScroll()

        updateUsersSearchParams({
            uQuery: value || null,
            uPage: null,
        })
    }, [resetUsersScroll, updateUsersSearchParams])

    const handleRoleFilterChange = useCallback((role: EmployeeRole | null) => {
        resetUsersScroll()

        updateUsersSearchParams({
            uRole: role,
            uPage: null,
        })
    }, [resetUsersScroll, updateUsersSearchParams])

    const handleUserSortChange = useCallback((
        option: typeof usersSortOptions[number],
    ) => {
        resetUsersScroll()

        updateUsersSearchParams({
            uSort: option.value === usersSortOptions[0].value ? null : option.value,
            uPage: null,
        })
    }, [resetUsersScroll, updateUsersSearchParams])

    const handleOpenAddEmployee = useCallback(() => {
        if (!canManageUsers) return

        setIsAddEmployeeOpen(true)
    }, [canManageUsers])

    const handleOpenReportSchedule = useCallback(() => {
        if (!canManageUsers) return

        setIsReportScheduleOpen(true)
    }, [canManageUsers])

    const handleReportScheduleSuccess = useCallback((
        schedule: FleetReportSchedule,
    ) => {
        setNotification({
            id: Date.now(),
            variant: 'success',
            title: 'Рассылка отчета сохранена',
            description: `Ежедневный отчет будет отправляться в ${schedule.sendTime}.`,
        })
    }, [])

    const handleEditEmployeeOpenChange = useCallback((open: boolean) => {
        if (!open) {
            setEditingEmployee(null)
        }
    }, [])

    const handleDeleteEmployeeOpenChange = useCallback((open: boolean) => {
        if (!open) {
            setDeletingEmployee(null)
        }
    }, [])

    return (
        <>
            <EmployeesToolbar
                addButtonText="Добавить пользователя"
                searchValue={userSearch}
                onSearchChange={handleUserSearchChange}
                sortOptions={usersSortOptions}
                selectedSort={userSort}
                roleFilter={roleFilter}
                onRoleFilterChange={handleRoleFilterChange}
                onSortChange={handleUserSortChange}
                showSort
                showUserFilters={false}
                showAddButton={canManageUsers}
                showSecondaryAction={canManageUsers}
                secondaryActionText="Рассылка отчета"
                onSecondaryAction={handleOpenReportSchedule}
                onAdd={handleOpenAddEmployee}
            />

            <m.div
                key="users-table"
                className="employees-page__table-motion"
                variants={pageSwapVariants}
                initial="initial"
                animate="animate"
                transition={uiFadeTransition}
            >
                <div className="employees-table-container">
                    <div className="employees-table-shadow">
                        <SimpleBar
                            scrollableNodeProps={tableScrollProps}
                            className={`employees-table-card ${isUsersUpdating ? 'is-updating' : ''
                                }`}
                        >
                            <RevealContent
                                loading={isUsersInitialLoading}
                                fallback={
                                    <TableSkeleton
                                        className="employees-table"
                                        columns={usersTableSkeletonColumns}
                                    />
                                }
                            >
                                {shouldShowUsersError && (
                                    <div className="employees-state employees-state--error">
                                        Не удалось загрузить пользователей
                                    </div>
                                )}

                                {!usersError && employees.length === 0 && (
                                    <EmptyState
                                        className="employees-state"
                                        variant={hasAppliedUsersFilters ? 'not-found' : 'no-items'}
                                        title={
                                            hasAppliedUsersFilters
                                                ? 'Не найдено результатов'
                                                : 'Пользователей пока нет'
                                        }
                                        description={
                                            hasAppliedUsersFilters
                                                ? 'По заданным параметрам не найдено ни одного пользователя. Попробуйте изменить поиск или фильтры.'
                                                : 'Добавьте пользователей, чтобы они появились в системе.'
                                        }
                                    />
                                )}

                                {employees.length > 0 && (
                                    <UsersTable
                                        employees={employees}
                                        canManageUsers={canManageUsers}
                                        onEdit={handleEditEmployee}
                                        onDelete={handleDeleteEmployee}
                                    />
                                )}
                            </RevealContent>
                        </SimpleBar>
                    </div>

                    {usersTotalPages > 1 && (
                        <EmployeesPagination
                            currentPage={usersCurrentPage}
                            loading={usersLoading}
                            items={usersPaginationItems}
                            hasPrevPage={hasPrevUsersPage}
                            hasNextPage={hasNextUsersPage}
                            onPrev={handlePrevUsersPage}
                            onNext={handleNextUsersPage}
                            onJumpPrev={handleJumpPrevUsersPage}
                            onJumpNext={handleJumpNextUsersPage}
                            onPageChange={setUsersPage}
                        />
                    )}
                </div>
            </m.div>

            <AddEmployeeModal
                open={isAddEmployeeOpen}
                mode="create"
                canChangeRole
                onOpenChange={setIsAddEmployeeOpen}
                onSuccess={refetchFirstUsersPage}
            />

            <AddEmployeeModal
                open={Boolean(editingEmployee)}
                mode="edit"
                employee={editingEmployee ?? renderEditingEmployee}
                canChangeRole
                onOpenChange={handleEditEmployeeOpenChange}
                onSuccess={refetchFirstUsersPage}
            />

            <FleetReportScheduleModal
                open={isReportScheduleOpen}
                onOpenChange={setIsReportScheduleOpen}
                onSuccess={handleReportScheduleSuccess}
            />

            <ConfirmDangerDialog
                open={Boolean(deletingEmployee)}
                title="Удалить пользователя?"
                description={
                    renderDeletingEmployee
                        ? `Пользователь «${getEmployeeFullName(
                            renderDeletingEmployee,
                        )}» будет удалён из системы. Это действие нельзя будет отменить.`
                        : ''
                }
                confirmText="Удалить"
                isLoading={isDeletingEmployee}
                onOpenChange={handleDeleteEmployeeOpenChange}
                onConfirm={handleConfirmDeleteEmployee}
            />

            {notification && (
                <Notification
                    key={notification.id}
                    variant={notification.variant}
                    title={notification.title}
                    description={notification.description}
                    onClose={() => setNotification(null)}
                />
            )}
        </>
    )
}
