<template>
  <UContainer class="page-shell">
    <div class="space-y-5">
      <div class="page-header">
        <UBadge label="Super Admin" color="primary" variant="soft" class="page-eyebrow" />
        <h1 class="page-title">
          User management
        </h1>
      </div>

      <UAlert
        v-if="issuedPasswordMessage"
        title="Temporary password issued"
        :description="issuedPasswordMessage"
        color="success"
        icon="i-lucide-key-round"
      />

      <UCard class="surface-card overflow-hidden rounded-lg" :ui="{ body: 'p-0 sm:p-0' }">
        <template #header>
          <div class="flex flex-col gap-2.5 lg:flex-row lg:items-center lg:justify-between">
            <div class="flex flex-col gap-2 sm:flex-row sm:items-center">
              <UInput
                v-model="searchQuery"
                size="lg"
                class="w-full sm:w-72"
                placeholder="Search name, username, or phone"
              />

              <UButton
                label="Refresh"
                color="neutral"
                variant="outline"
                icon="i-lucide-refresh-cw"
                size="lg"
                :loading="loadingUsers"
                @click="refreshUsers"
              />
            </div>

            <UButton
              label="Add User"
              size="lg"
              icon="i-lucide-plus"
              class="justify-center"
              @click="openCreateModal"
            />
          </div>
        </template>

        <div class="overflow-x-auto">
          <div class="compact-table min-w-[900px]" aria-label="Users">
            <div class="grid grid-cols-[56px_minmax(190px,1.4fr)_minmax(150px,1fr)_120px_minmax(190px,1.2fr)_150px_minmax(230px,auto)] items-center gap-3 border-b border-default bg-muted px-4 py-3 text-xs font-semibold uppercase text-muted">
              <div>Order</div>
              <div>Display Name</div>
              <div>PIC Phone</div>
              <div>Role</div>
              <div>Status</div>
              <div>Last Login</div>
              <div class="text-right">Actions</div>
            </div>

            <TransitionGroup tag="div" name="user-list" class="divide-y divide-default">
              <div
                v-for="user in filteredUsers"
                :key="user.id"
                :draggable="canReorderUsers"
                class="grid grid-cols-[56px_minmax(190px,1.4fr)_minmax(150px,1fr)_120px_minmax(190px,1.2fr)_150px_minmax(230px,auto)] items-center gap-3 bg-default px-4 py-3 transition-[background,box-shadow,transform,opacity] duration-200"
                :class="{
                  'cursor-grab hover:bg-muted/60': canReorderUsers,
                  'cursor-not-allowed opacity-70': !canReorderUsers,
                  'scale-[0.99] opacity-60 shadow-lg': draggedUserId === user.id,
                  'bg-muted': dragOverUserId === user.id
                }"
                @dragstart="startUserDrag($event, user)"
                @dragover.prevent="overUserDrag($event, user)"
                @drop.prevent="dropUserDrag"
                @dragend="endUserDrag"
              >
                <div class="flex items-center">
                  <UTooltip :text="canReorderUsers ? 'Drag to reorder' : 'Clear search to reorder'">
                    <button
                      type="button"
                      class="inline-flex size-9 items-center justify-center rounded-md border border-default bg-default text-muted transition hover:text-default focus:outline-none focus-visible:ring-2 focus-visible:ring-primary"
                      :class="canReorderUsers ? 'cursor-grab active:cursor-grabbing' : 'cursor-not-allowed opacity-50'"
                      :aria-label="`Drag ${user.fullName} to reorder PIC list`"
                      :disabled="!canReorderUsers"
                    >
                      <UIcon name="i-lucide-grip-vertical" class="size-4" />
                    </button>
                  </UTooltip>
                </div>

                <div class="min-w-0 space-y-0.5 py-1">
                  <div class="font-semibold leading-tight text-highlighted">
                    {{ user.fullName }}
                  </div>
                  <div class="text-xs text-muted">
                    @{{ user.username }}
                  </div>
                </div>

                <span class="text-sm" :class="user.phoneNumber ? 'text-default' : 'text-muted'">
                  {{ user.phoneNumber || 'Not set' }}
                </span>

                <div>
                  <UBadge
                    :label="user.role === 'super_admin' ? 'Super Admin' : 'Staff'"
                    :color="user.role === 'super_admin' ? 'primary' : 'neutral'"
                    variant="soft"
                  />
                </div>

                <div class="space-y-1.5 py-1">
                  <div class="flex flex-wrap gap-1.5">
                    <UBadge
                      :label="user.mustChangePassword ? 'Password reset' : 'Password ready'"
                      :color="user.mustChangePassword ? 'warning' : 'success'"
                      variant="soft"
                      size="sm"
                    />
                    <UBadge
                      :label="user.passkeyCount > 0 ? 'Passkey ready' : 'No passkey'"
                      :color="user.passkeyCount > 0 ? 'success' : 'neutral'"
                      variant="soft"
                      size="sm"
                    />
                  </div>

                  <div class="text-xs text-muted">
                    {{ user.passkeyCount }} passkey{{ user.passkeyCount === 1 ? '' : 's' }}
                  </div>
                </div>

                <span class="text-xs text-muted sm:text-sm">
                  {{ formatDateTime(user.lastLoginAt, 'Never') }}
                </span>

                <div class="flex flex-wrap justify-end gap-1.5 py-1">
                  <UButton
                    label="Edit"
                    color="neutral"
                    variant="outline"
                    icon="i-lucide-pencil-line"
                    size="sm"
                    @click="openEditModal(user)"
                  />

                  <UButton
                    label="Reset Password"
                    color="neutral"
                    variant="outline"
                    icon="i-lucide-key-round"
                    size="sm"
                    :loading="resettingUserId === user.id"
                    @click="resetPassword(user)"
                  />
                </div>
              </div>
            </TransitionGroup>

            <div v-if="!filteredUsers.length" class="px-4 py-10 text-center text-sm text-muted">
              {{ searchQuery.trim() ? 'No matching users found.' : 'No users available.' }}
            </div>
          </div>
        </div>
      </UCard>

      <UModal
        v-model:open="editorOpen"
        :title="isEditMode ? 'Edit User' : 'Add User'"
        :dismissible="!savingUser"
        :close="!savingUser"
        :content="{ class: 'sm:max-w-xl' }"
      >
        <template #body>
          <UForm :state="userForm" :validate="validateUserForm" class="space-y-4" @submit="saveUser">
            <UFormField name="fullName" label="Display Name" required>
              <UInput v-model="userForm.fullName" size="lg" class="w-full" />
            </UFormField>

            <UFormField name="username" label="Username" required>
              <UInput
                v-model="userForm.username"
                size="lg"
                class="w-full"
                :disabled="isEditMode"
              />
            </UFormField>

            <UFormField name="phoneNumber" label="Phone Number" required>
              <UInput
                v-model="userForm.phoneNumber"
                size="lg"
                type="tel"
                class="w-full"
                placeholder="e.g. +60123456789"
              />
            </UFormField>

            <UFormField name="role" label="Role" required>
              <USelect
                v-model="userForm.role"
                size="lg"
                class="w-full"
                :items="roleOptions"
                :disabled="isEditingCurrentUser"
              />
            </UFormField>

            <div class="flex flex-col-reverse gap-2 sm:flex-row sm:justify-end">
              <UButton
                label="Cancel"
                color="neutral"
                variant="ghost"
                class="justify-center"
                :disabled="savingUser"
                @click="closeEditor"
              />

              <UButton
                type="submit"
                :label="isEditMode ? 'Save Changes' : 'Create User'"
                class="justify-center"
                :loading="savingUser"
              />
            </div>
          </UForm>
        </template>
      </UModal>
    </div>
  </UContainer>
</template>

<script lang="ts" setup>
import type { FormError, FormSubmitEvent } from '@nuxt/ui'

import {
  DEFAULT_PHONE_COUNTRY_CODE,
  hasValidFullName,
  isValidPhoneNumber,
  isValidUsername,
  normalizeFullName,
  normalizePhoneNumber,
  normalizeUsername,
  type ManagedUser,
  type UserRole
} from '~~/shared/auth'

import { getErrorMessage } from '../../../utils/errors'
import { formatDateTime } from '../../../utils/formatters'

definePageMeta({
  middleware: 'super-admin'
})

const toast = useToast()
const apiClient = useApiClient()
const auth = useAuth()

const users = ref<ManagedUser[]>([])
const loadingUsers = ref(false)
const savingUser = ref(false)
const savingPicOrder = ref(false)
const resettingUserId = ref<string | null>(null)
const issuedPasswordMessage = ref('')
const searchQuery = ref('')
const editorOpen = ref(false)
const editorMode = ref<'create' | 'edit'>('create')
const editingUserId = ref<string | null>(null)
const draggedUserId = ref<string | null>(null)
const dragOverUserId = ref<string | null>(null)
const dragSaveStarted = ref(false)
let usersBeforeDrag: ManagedUser[] = []

const userForm = reactive({
  fullName: '',
  username: '',
  phoneNumber: DEFAULT_PHONE_COUNTRY_CODE,
  role: 'staff' as UserRole
})

const roleOptions = [
  { label: 'Staff', value: 'staff' },
  { label: 'Super Admin', value: 'super_admin' }
]

const isEditMode = computed(() => editorMode.value === 'edit')
const isEditingCurrentUser = computed(() => {
  return isEditMode.value && editingUserId.value === auth.user.value?.id
})
const canReorderUsers = computed(() => {
  return !searchQuery.value.trim() && !loadingUsers.value && !savingPicOrder.value
})

const filteredUsers = computed(() => {
  const keyword = searchQuery.value.trim().toLowerCase()

  if (!keyword) {
    return users.value
  }

  return users.value.filter((user) => {
    return [
      user.fullName,
      user.username,
      user.phoneNumber || '',
      user.role
    ].some((value) => value.toLowerCase().includes(keyword))
  })
})

await refreshUsers()

function resetUserForm() {
  userForm.fullName = ''
  userForm.username = ''
  userForm.phoneNumber = DEFAULT_PHONE_COUNTRY_CODE
  userForm.role = 'staff'
  editingUserId.value = null
}

function openCreateModal() {
  editorMode.value = 'create'
  resetUserForm()
  editorOpen.value = true
}

function openEditModal(user: ManagedUser) {
  editorMode.value = 'edit'
  editingUserId.value = user.id
  userForm.fullName = user.fullName
  userForm.username = user.username
  userForm.phoneNumber = user.phoneNumber ? normalizePhoneNumber(user.phoneNumber) : DEFAULT_PHONE_COUNTRY_CODE
  userForm.role = user.role
  editorOpen.value = true
}

function closeEditor() {
  if (savingUser.value) {
    return
  }

  editorOpen.value = false
  resetUserForm()
}

function validateUserForm(state: typeof userForm): FormError[] {
  const errors: FormError[] = []

  if (!hasValidFullName(state.fullName)) {
    errors.push({ name: 'fullName', message: 'Enter a display name with at least 2 characters.' })
  }

  if (!isEditMode.value && !isValidUsername(state.username)) {
    errors.push({ name: 'username', message: 'Use 3 to 32 lowercase letters, numbers, dot, dash, or underscore.' })
  }

  if (!isValidPhoneNumber(state.phoneNumber)) {
    errors.push({ name: 'phoneNumber', message: 'Use a valid phone number with country code, e.g. +60123456789.' })
  }

  return errors
}

async function refreshUsers() {
  if (loadingUsers.value) {
    return
  }

  loadingUsers.value = true

  try {
    const response = await apiClient<{ users: ManagedUser[] }>('/api/admin/users')
    users.value = response.users
  } catch (error: any) {
    toast.add({
      title: 'Unable to load users',
      description: getErrorMessage(error, 'The user list could not be loaded.'),
      color: 'error',
      icon: 'i-lucide-circle-alert'
    })
  } finally {
    loadingUsers.value = false
  }
}

function moveUserNearTarget(sourceUserId: string, targetUserId: string, insertAfterTarget: boolean) {
  if (sourceUserId === targetUserId) {
    return
  }

  const sourceIndex = users.value.findIndex((user) => user.id === sourceUserId)
  const currentUserIds = users.value.map((user) => user.id)

  if (sourceIndex === -1 || !currentUserIds.includes(targetUserId)) {
    return
  }

  const reorderedUsers = [...users.value]
  const [movedUser] = reorderedUsers.splice(sourceIndex, 1)
  const targetIndex = reorderedUsers.findIndex((user) => user.id === targetUserId)

  if (targetIndex === -1) {
    return
  }

  reorderedUsers.splice(insertAfterTarget ? targetIndex + 1 : targetIndex, 0, movedUser)

  const nextUserIds = reorderedUsers.map((user) => user.id)

  if (nextUserIds.join('|') === currentUserIds.join('|')) {
    return
  }

  users.value = reorderedUsers
}

function resetDragState() {
  draggedUserId.value = null
  dragOverUserId.value = null
  dragSaveStarted.value = false
  usersBeforeDrag = []
}

function startUserDrag(event: DragEvent, user: ManagedUser) {
  if (!canReorderUsers.value) {
    event.preventDefault()
    return
  }

  draggedUserId.value = user.id
  dragOverUserId.value = user.id
  dragSaveStarted.value = false
  usersBeforeDrag = [...users.value]
  event.dataTransfer?.setData('text/plain', user.id)

  if (event.dataTransfer) {
    event.dataTransfer.effectAllowed = 'move'
  }
}

function overUserDrag(event: DragEvent, user: ManagedUser) {
  if (event.dataTransfer) {
    event.dataTransfer.dropEffect = 'move'
  }

  if (!canReorderUsers.value || !draggedUserId.value || draggedUserId.value === user.id) {
    return
  }

  const target = event.currentTarget

  if (!(target instanceof HTMLElement)) {
    return
  }

  const rect = target.getBoundingClientRect()
  const insertAfterTarget = event.clientY > rect.top + rect.height / 2

  dragOverUserId.value = user.id
  moveUserNearTarget(draggedUserId.value, user.id, insertAfterTarget)
}

async function dropUserDrag() {
  if (!draggedUserId.value || savingPicOrder.value) {
    return
  }

  dragSaveStarted.value = true
  savingPicOrder.value = true

  try {
    const response = await apiClient<{ users: ManagedUser[] }>('/api/admin/users/order', {
      method: 'PATCH',
      body: {
        userIds: users.value.map((user) => user.id)
      }
    })

    users.value = response.users
    toast.add({
      title: 'PIC order saved',
      color: 'success',
      icon: 'i-lucide-check-circle-2'
    })
  } catch (error: any) {
    users.value = usersBeforeDrag
    toast.add({
      title: 'PIC order could not be saved',
      description: getErrorMessage(error, 'Please try again in a moment.'),
      color: 'error',
      icon: 'i-lucide-circle-alert'
    })
  } finally {
    savingPicOrder.value = false
    resetDragState()
  }
}

function endUserDrag() {
  if (dragSaveStarted.value) {
    return
  }

  if (draggedUserId.value && usersBeforeDrag.length) {
    users.value = usersBeforeDrag
  }

  resetDragState()
}

async function saveUser(event: FormSubmitEvent<typeof userForm>) {
  event.preventDefault()

  if (savingUser.value) {
    return
  }

  savingUser.value = true

  try {
    if (isEditMode.value && editingUserId.value) {
      await apiClient(`/api/admin/users/${editingUserId.value}`, {
        method: 'PATCH',
        body: {
          fullName: normalizeFullName(userForm.fullName),
          phoneNumber: normalizePhoneNumber(userForm.phoneNumber),
          role: userForm.role
        }
      })

      if (editingUserId.value === auth.user.value?.id) {
        await auth.refreshSession()
      }

      toast.add({
        title: 'User updated',
        description: `${normalizeFullName(userForm.fullName)} has been updated.`,
        color: 'success',
        icon: 'i-lucide-check-circle-2'
      })
    } else {
      const response = await apiClient<{
        user: ManagedUser
        defaultPassword: string
      }>('/api/admin/users', {
        method: 'POST',
        body: {
          fullName: normalizeFullName(userForm.fullName),
          username: normalizeUsername(userForm.username),
          phoneNumber: normalizePhoneNumber(userForm.phoneNumber),
          role: userForm.role
        }
      })

      issuedPasswordMessage.value = `${response.user.fullName} was created with the temporary password ${response.defaultPassword}.`

      toast.add({
        title: 'User created',
        description: `${response.user.fullName} can now sign in with the temporary password.`,
        color: 'success',
        icon: 'i-lucide-check-circle-2'
      })
    }

    closeEditor()
    await refreshUsers()
  } catch (error: any) {
    toast.add({
      title: isEditMode.value ? 'Update failed' : 'User creation failed',
      description: getErrorMessage(
        error,
        isEditMode.value ? 'Unable to update this user.' : 'Unable to create the new user.'
      ),
      color: 'error',
      icon: 'i-lucide-circle-alert'
    })
  } finally {
    savingUser.value = false
  }
}

async function resetPassword(user: ManagedUser) {
  if (resettingUserId.value) {
    return
  }

  resettingUserId.value = user.id

  try {
    const response = await apiClient<{
      user: ManagedUser
      defaultPassword: string
    }>(`/api/admin/users/${user.id}/reset-password`, {
      method: 'POST'
    })

    issuedPasswordMessage.value = `${user.fullName}'s password was reset to ${response.defaultPassword}. They must change it after login.`

    toast.add({
      title: 'Password reset',
      description: `${user.fullName} now has a fresh temporary password.`,
      color: 'success',
      icon: 'i-lucide-check-circle-2'
    })

    await refreshUsers()
  } catch (error: any) {
    toast.add({
      title: 'Reset failed',
      description: getErrorMessage(error, 'Unable to reset this password.'),
      color: 'error',
      icon: 'i-lucide-circle-alert'
    })
  } finally {
    resettingUserId.value = null
  }
}
</script>

<style scoped>
.user-list-move,
.user-list-enter-active,
.user-list-leave-active {
  transition: transform 180ms ease, opacity 160ms ease, background-color 160ms ease;
}

.user-list-enter-from,
.user-list-leave-to {
  opacity: 0;
  transform: translateY(6px);
}

.user-list-leave-active {
  position: absolute;
  width: 100%;
}
</style>
