import {
  ActionIcon,
  Box,
  Button,
  Center,
  Group,
  Loader,
  Menu,
  SegmentedControl,
  Select,
  TextInput,
  notifier
} from '@tidbcloud/uikit'
import { MRT_ColumnDef, MRT_SortingState, ProTable } from '@tidbcloud/uikit/biz'
import { useDebouncedValue } from '@tidbcloud/uikit/hooks'
import { IconDotsHorizontal, IconSearchSm, IconXClose } from '@tidbcloud/uikit/icons'
import { useMemoizedFn } from 'ahooks'
import { observer } from 'mobx-react'
import { useMemo, useState } from 'react'
import { useQueryClient } from 'react-query'

import { HeroComponent } from 'dbaas/components/Measurement'
import TimeComponent from 'dbaas/components/TimeComponent'
import { AccessDeniedHoverHint, AccessGuard, RBACRoleUINames, useAccessCheck } from 'dbaas/contexts/access-control'
import { PageShell } from 'dbaas/layouts/v5/page'
import {
  getListProjectUsersQueryKey,
  useListProjectUsers,
  useListUsers,
  useUpdateProjectUserRole,
  useUpdateUser
} from 'dbaas/services/account'
import { CentralUser } from 'dbaas/services/account/interface'
import { getErrorMessage } from 'dbaas/services/errorCodes'
import useStores from 'dbaas/stores/useStores'
import { eventTracking } from 'dbaas/utils/tracking'

import { orgRoleOptions, projectRoleOptions } from '../constant'
import { OrgInvitationModal, ProjectInvitationModal } from '../InviteUserModal'
import { ProjectsPlaceholder } from '../Projects/EmptyProjectsPlaceholder'

import { UserDeletionModal } from './DeletionModal'
import { InvitationModeConst, InvitationModes, ScopeTypeConst, ScopeTypes, useDistinctUntilChanged } from './helpers'

interface EditableProjectRoleCellProps {
  data: CentralUser
  orgId: string
  projectId: string
  onChange?: () => void
}

const EditableProjectRoleCell = observer(({ data, orgId, projectId, onChange }: EditableProjectRoleCellProps) => {
  const [value, setValue] = useDistinctUntilChanged(data.rbac_role)

  const roleMutation = useUpdateProjectUserRole({
    mutation: {
      onSuccess: (_response, { data: { rbac_role: role } }) => {
        setValue(role)
        onChange?.()
      },
      onError: (error) => {
        const errMsg = getErrorMessage(error)

        notifier.error(errMsg)
      }
    }
  })

  const handleChange = async (role: string) => {
    return roleMutation.mutateAsync({
      orgId,
      projectId,
      userId: data.id,
      data: {
        rbac_role: role
      }
    })
  }

  return (
    <Select
      variant="unstyled"
      styles={{ input: { minHeight: 32, height: 32, backgroundColor: 'transparent' } }}
      data={projectRoleOptions}
      value={value}
      onChange={handleChange}
      required
      withAsterisk={false}
      disabled={roleMutation.isLoading}
      rightSection={roleMutation.isLoading ? <Loader size={14} /> : undefined}
      w={272}
    />
  )
})

const ProjectUserList = ({
  orgId,
  projectId,
  onInvitationOpen,
  onDeletionOpen,
  search,
  currentPage,
  onPageChange
}: {
  orgId: string
  projectId: string
  search?: string
  currentPage: number
  onPageChange: (page: number) => void
  onInvitationOpen: (user?: CentralUser) => void
  onDeletionOpen: (user: CentralUser, projectId?: string) => void
}) => {
  const {
    store: {
      userProfile: { email }
    }
  } = useStores()

  const pageSize = 10
  const [projectSorting, setProjectSorting] = useState<MRT_SortingState>([])
  const { data, isLoading, refetch } = useListProjectUsers(orgId, projectId, {
    page: currentPage,
    per_page: pageSize,
    email: search ? encodeURIComponent(search) : undefined,
    sort_by: projectSorting[0]?.id ?? undefined,
    order: projectSorting[0] ? (projectSorting[0].desc ? 'desc' : 'asc') : undefined
  })
  const projectLoginTimeAccessible = useAccessCheck('ProjectUsersLoginTimeGate', {
    projectId
  })
  const projectUsersActionAccessible = useAccessCheck('ProjectUsersAction', {
    projectId
  })
  const columns = useMemo<MRT_ColumnDef<CentralUser>[]>(
    () => [
      {
        header: 'User',
        accessorKey: 'email',
        mantineTableHeadCellProps: {
          pl: '24px !important'
        },
        mantineTableBodyCellProps: {
          pl: '24px !important'
        }
      },
      {
        header: 'Role',
        id: 'role',
        accessorFn: (row: CentralUser) => (
          <AccessGuard
            key={row.id}
            id="ProjectUsersAction"
            scopeProps={{ projectId }}
            fallback={RBACRoleUINames[row.rbac_role]}
          >
            <EditableProjectRoleCell data={row} orgId={orgId} projectId={projectId} onChange={refetch} />
          </AccessGuard>
        )
      },
      {
        header: 'Invite Time',
        id: 'invite_time',
        maxSize: 200,
        accessorFn: (row) => <TimeComponent time={row.invite_time} />
      },
      {
        header: 'Status',
        accessorKey: 'status',
        maxSize: 100
      },
      {
        header: 'Last Login Time',
        id: 'login_time',
        accessorFn(originalRow) {
          if (!originalRow.login_time) {
            return originalRow.status === 'ACTIVE' ? '-' : 'Has not logged in yet'
          }
          return <TimeComponent time={originalRow.login_time} />
        }
      },
      {
        header: 'Action',
        id: 'action',
        Header: <Box px={8}>Actions</Box>,
        accessorFn: (row: CentralUser) => {
          const showResend = row.status !== 'ACTIVE'
          const showDelete = row.email !== email
          if (!showResend && !showDelete) {
            return <Center w={32}>-</Center>
          }
          return (
            <Menu withinPortal>
              <Menu.Target>
                <ActionIcon w={32} h={32} variant="subtle" data-mp-event="User Management Actions Clicked">
                  <IconDotsHorizontal size={16} />
                </ActionIcon>
              </Menu.Target>
              <Menu.Dropdown>
                {showResend && (
                  <Menu.Item
                    disabled={!projectUsersActionAccessible}
                    onClick={() => {
                      eventTracking('User Management Resend Button Clicked', {
                        type: ScopeTypeConst.Project
                      })
                      onInvitationOpen(row)
                    }}
                  >
                    Resend
                  </Menu.Item>
                )}
                {showDelete && (
                  <Menu.Item
                    disabled={!projectUsersActionAccessible}
                    onClick={() => {
                      eventTracking('mp-delete-1691461016447')
                      onDeletionOpen(row, projectId)
                    }}
                  >
                    Delete
                  </Menu.Item>
                )}
              </Menu.Dropdown>
            </Menu>
          )
        },
        enableSorting: false
      }
    ],
    [email, onDeletionOpen, onInvitationOpen, orgId, projectId, projectUsersActionAccessible, refetch]
  )

  return (
    <ProTable
      enableSorting={true}
      manualSorting={true}
      columns={columns}
      data={data?.items || []}
      loading={isLoading}
      emptyMessage="No Data"
      onSortingChange={(data) => {
        onPageChange(1)
        setProjectSorting(data)
      }}
      state={{
        sorting: projectSorting,
        columnVisibility: {
          login_time: projectLoginTimeAccessible
        }
      }}
      manualPagination={true}
      pagination={
        data?.total
          ? {
              total: Math.ceil((data?.total || 0) / pageSize) || 0,
              value: currentPage,
              onChange: onPageChange
            }
          : void 0
      }
    />
  )
}

interface EditableOrgRoleCellProps {
  data: CentralUser
  orgId: string
  onChange?: () => void
}

const EditableOrgRoleCell = ({ data, orgId, onChange }: EditableOrgRoleCellProps) => {
  const [value, setValue] = useState(data.rbac_role)

  const roleMutation = useUpdateUser({
    mutation: {
      onSuccess: (_response, { data: { rbac_role: role } }) => {
        setValue(role)
        onChange?.()
      },
      onError: (error) => {
        const errMsg = getErrorMessage(error)

        notifier.error(errMsg)
      }
    }
  })

  const handleChange = async (role: string) => {
    return roleMutation.mutateAsync({
      orgId,
      userId: data.id,
      data: {
        rbac_role: role
      }
    })
  }

  return (
    <Select
      variant="unstyled"
      data={orgRoleOptions}
      value={value}
      styles={{ input: { minHeight: 32, height: 32, backgroundColor: 'transparent' } }}
      onChange={handleChange}
      required
      withAsterisk={false}
      disabled={roleMutation.isLoading}
      rightSection={roleMutation.isLoading ? <Loader size={14} /> : undefined}
      w={262}
    />
  )
}

export const UserAccess = observer(() => {
  const queryClient = useQueryClient()
  const {
    store: {
      tenantData: { id: orgId },
      userProfile: { email, projects }
    }
  } = useStores()

  const orgLoginTimeAccessible = useAccessCheck('OrgUsersLoginTimeGate')
  const orgUsersActionAccessible = useAccessCheck('OrgUsersAction')
  const [orgSorting, setOrgSorting] = useState<MRT_SortingState>([])

  const [invitedProject, setInvitedProject] = useState(projects[0]?.id || '')
  const [currentPage, setCurrentPage] = useState(1)
  const [search, setSearch] = useState('')
  const onProjectChange = (value: string) => {
    setInvitedProject(value)
    setSearch('')
    setOrgSorting([])
    setCurrentPage(1)
  }

  const [debouncedSearch] = useDebouncedValue(search, 500)
  const [segmentType, setSegmentType] = useState<string>(ScopeTypeConst.Org)
  const onSegmentTypeChange = useMemoizedFn((type: string) => {
    setSegmentType(type)
    setSearch('')
    setCurrentPage(1)
    setOrgSorting([])

    if (type === ScopeTypeConst.Project) {
      const projectExisted = projects.find((p) => p.id === invitedProject)

      if (!projectExisted) {
        setInvitedProject(projects[0]?.id || '')
      }
    }
  })

  /**
   * Org invitation
   */
  const [orgInvitationOpened, setOrgInvitationOpened] = useState(false)
  const [orgInvitationMode, setOrgInvitationMode] = useState<InvitationModes>(InvitationModeConst.Free)
  const [orgInvitedUser, setOrgInvitedUser] = useState<CentralUser>()
  const openOrgInvitationModal = useMemoizedFn((user?: CentralUser) => {
    if (user) {
      setOrgInvitationMode(InvitationModeConst.Specific)
      setOrgInvitedUser(user)
    } else {
      setOrgInvitationMode(InvitationModeConst.Free)
      setOrgInvitedUser(void 0)
    }

    setOrgInvitationOpened(true)
  })
  const closeOrgInvitationModal = () => setOrgInvitationOpened(false)

  /**
   * Project invitation
   */
  const [projectInvitationOpened, setProjectInvitationOpened] = useState(false)
  const [projectInvitationMode, setProjectInvitationMode] = useState<InvitationModes>(InvitationModeConst.Free)
  const [projectInvitedUser, setProjectInvitedUser] = useState<CentralUser>()
  const openProjectInvitationModal = useMemoizedFn((user?: CentralUser) => {
    if (user) {
      setProjectInvitationMode(InvitationModeConst.Specific)
      setProjectInvitedUser(user)
    } else {
      setProjectInvitationMode(InvitationModeConst.Free)
      setProjectInvitedUser(void 0)
    }

    setProjectInvitationOpened(true)
  })
  const closeProjectInvitationModal = () => setProjectInvitationOpened(false)

  /**
   * User deletion
   */
  const [deletionOpened, setDeletionOpened] = useState(false)
  const [deletedUser, setDeletedUser] = useState<CentralUser>()
  const [deletedScopeId, setDeletedScopeId] = useState('')
  const openDeletionModal = useMemoizedFn((user: CentralUser, projectId?: string) => {
    setDeletionOpened(true)
    setDeletedUser(user)
    setDeletedScopeId(projectId || (segmentType === ScopeTypeConst.Org ? orgId : ''))
  })
  const closeDeletionModal = () => setDeletionOpened(false)

  /**
   * Org user list
   */
  const pageSize = 10
  const { data, isLoading, refetch, isFetched } = useListUsers(
    orgId,
    {
      page: currentPage,
      per_page: pageSize,
      email: debouncedSearch ? encodeURIComponent(debouncedSearch) : undefined,
      sort_by: orgSorting[0]?.id ?? undefined,
      order: orgSorting[0] ? (orgSorting[0].desc ? 'desc' : 'asc') : undefined
    },
    {
      query: {
        enabled: segmentType === ScopeTypeConst.Org
      }
    }
  )

  const onProjectUpdate = (projectId: string) =>
    queryClient.invalidateQueries(getListProjectUsersQueryKey(orgId, projectId))

  const onDeletionFinish = () => {
    if (segmentType === ScopeTypeConst.Org) {
      refetch()
    } else {
      onProjectUpdate(deletedScopeId)
    }
  }

  const columns = useMemo<MRT_ColumnDef<CentralUser>[]>(
    () => [
      {
        header: 'User',
        accessorKey: 'email',
        maxSize: 240,
        mantineTableHeadCellProps: {
          pl: '24px !important'
        },
        mantineTableBodyCellProps: {
          pl: '24px !important'
        }
      },
      {
        header: 'Role',
        id: 'role',
        accessorFn: (row: CentralUser) => (
          <AccessGuard key={row.id} id="OrgUsersAction" fallback={RBACRoleUINames[row.rbac_role]}>
            <EditableOrgRoleCell data={row} orgId={orgId} onChange={refetch} />
          </AccessGuard>
        )
      },
      {
        header: 'Invite Time',
        id: 'invite_time',
        maxSize: 200,
        accessorFn: (row) => <TimeComponent time={row.invite_time} />
      },
      {
        header: 'Status',
        accessorKey: 'status',
        maxSize: 100
      },
      {
        header: 'Last Login Time',
        id: 'login_time',
        mantineTableHeadCellProps: {
          display: orgLoginTimeAccessible ? undefined : 'none'
        },
        mantineTableBodyCellProps: {
          display: orgLoginTimeAccessible ? undefined : 'none'
        },
        accessorFn(originalRow) {
          if (!originalRow.login_time) {
            return originalRow.status === 'ACTIVE' ? '-' : 'Has not logged in yet'
          }
          return <TimeComponent time={originalRow.login_time} />
        }
      },
      {
        header: 'Action',
        id: 'action',
        Header: <Box px={8}>Actions</Box>,
        accessorFn: (row: CentralUser) => {
          const showResend = row.status !== 'ACTIVE'
          const showDelete = row.email !== email
          if (!showResend && !showDelete) {
            return <Center w={32}>-</Center>
          }
          return (
            <Menu withinPortal>
              <Menu.Target>
                <ActionIcon w={32} h={32} variant="subtle" data-mp-event="User Management Actions Clicked">
                  <IconDotsHorizontal size={16} />
                </ActionIcon>
              </Menu.Target>
              <Menu.Dropdown>
                {showResend && (
                  <Menu.Item
                    disabled={!orgUsersActionAccessible}
                    onClick={() => {
                      eventTracking('User Management Resend Button Clicked', {
                        type: ScopeTypeConst.Org
                      })
                      openOrgInvitationModal(row)
                    }}
                  >
                    Resend
                  </Menu.Item>
                )}
                {showDelete && (
                  <Menu.Item
                    disabled={!orgUsersActionAccessible}
                    onClick={() => {
                      eventTracking('mp-delete-1691461016450')
                      openDeletionModal(row)
                    }}
                  >
                    Delete
                  </Menu.Item>
                )}
              </Menu.Dropdown>
            </Menu>
          )
        },
        enableSorting: false
      }
    ],
    [orgLoginTimeAccessible, orgId, refetch, email, orgUsersActionAccessible, openOrgInvitationModal, openDeletionModal]
  )

  return (
    <PageShell
      title="Users"
      headerProps={{
        sticky: true,
        rightSection: (
          <>
            {segmentType === ScopeTypeConst.Org && (
              <AccessDeniedHoverHint id="OrgUsersAction">
                {({ accessible }) => (
                  <Button
                    size="sm"
                    disabled={!accessible}
                    onClick={() => openOrgInvitationModal()}
                    data-mp-event="User Management Invite Button Clicked"
                    data-mp-type={ScopeTypeConst.Org}
                  >
                    Invite
                  </Button>
                )}
              </AccessDeniedHoverHint>
            )}
            {segmentType === ScopeTypeConst.Project && (
              <AccessDeniedHoverHint id="ProjectUsersAction" scopeProps={{ projectId: invitedProject }}>
                {({ accessible }) => (
                  <Button
                    size="sm"
                    disabled={!accessible}
                    onClick={() => openProjectInvitationModal()}
                    data-mp-event="User Management Invite Button Clicked"
                    data-mp-type={ScopeTypeConst.Project}
                  >
                    Invite
                  </Button>
                )}
              </AccessDeniedHoverHint>
            )}
          </>
        )
      }}
    >
      <Group>
        <Group>
          <SegmentedControl
            value={segmentType}
            onChange={onSegmentTypeChange}
            data={[
              { label: 'By Organization', value: ScopeTypeConst.Org },
              { label: 'By Project', value: ScopeTypeConst.Project }
            ]}
            py={5}
          />
          {segmentType === ScopeTypeConst.Project && !!projects.length && (
            <Select
              data={projects.map(({ id, name }) => ({ value: id, label: name }))}
              value={invitedProject}
              onChange={onProjectChange}
            />
          )}
        </Group>
        <TextInput
          placeholder="Search User"
          value={search}
          rightSection={
            !!search ? (
              <IconXClose
                size={14}
                sx={{ cursor: 'pointer' }}
                onClick={() => {
                  setSearch('')
                  setCurrentPage(1)
                }}
              />
            ) : (
              <IconSearchSm size={14} sx={{ cursor: 'pointer' }} />
            )
          }
          onChange={(event) => {
            setCurrentPage(1)
            setSearch(event.currentTarget.value)
          }}
        />
      </Group>

      {segmentType === ScopeTypeConst.Org && (
        <Box mt="xl" mb={8}>
          <ProTable
            columns={columns}
            data={data?.items || []}
            loading={isLoading}
            enableSorting={true}
            emptyMessage="No Data"
            state={{
              sorting: orgSorting,
              columnVisibility: {
                login_time: orgLoginTimeAccessible
              }
            }}
            manualSorting={true}
            onSortingChange={(data) => {
              setCurrentPage(1)
              setOrgSorting(data)
            }}
            manualPagination={true}
            pagination={
              data?.total
                ? {
                    total: Math.ceil((data?.total || 0) / pageSize) || 0,
                    value: currentPage,
                    onChange: setCurrentPage
                  }
                : void 0
            }
          />
        </Box>
      )}

      {segmentType === ScopeTypeConst.Project &&
        (!!projects.length ? (
          <Box mt="xl" mb={8}>
            <ProjectUserList
              orgId={orgId}
              projectId={invitedProject}
              onInvitationOpen={openProjectInvitationModal}
              onDeletionOpen={openDeletionModal}
              search={debouncedSearch}
              currentPage={currentPage}
              onPageChange={setCurrentPage}
            />
          </Box>
        ) : (
          <ProjectsPlaceholder />
        ))}

      <UserDeletionModal
        scopeType={segmentType as ScopeTypes}
        scopeId={deletedScopeId}
        user={deletedUser}
        opened={deletionOpened}
        onClose={closeDeletionModal}
        onFinish={onDeletionFinish}
      />
      <OrgInvitationModal
        mode={orgInvitationMode}
        user={orgInvitedUser}
        opened={orgInvitationOpened}
        onClose={closeOrgInvitationModal}
        onFinish={refetch}
      />
      <ProjectInvitationModal
        mode={projectInvitationMode}
        user={projectInvitedUser}
        projectId={invitedProject}
        opened={projectInvitationOpened}
        onClose={closeProjectInvitationModal}
        onFinish={() => onProjectUpdate(invitedProject)}
      />
      {isFetched && <HeroComponent />}
    </PageShell>
  )
})
