<template>
  <div class="active-sessions-panel">
    <a-alert
      v-if="error"
      :message="error"
      type="error"
      show-icon
      closable
      class="active-sessions-panel__alert"
      @close="error = ''"
    />

    <a-spin :spinning="effectiveLoading">
      <a-table
        :columns="columns"
        :data-source="displaySessions"
        :row-key="(record: ISessionInfo) => record.id"
        :pagination="false"
        size="middle"
        :row-class-name="rowClass"
      >
        <template #bodyCell="{ column, record }: { column: TableColumnType<ISessionInfo>; record: ISessionInfo }">
          <template v-if="column.key === 'device'">
            <div class="active-sessions-panel__device">
              <strong>{{ record.device_label || '未知设备' }}</strong>
              <a-tag v-if="record.is_current" color="blue" class="active-sessions-panel__tag">
                当前会话
              </a-tag>
              <a-tag v-if="adminMode && record.revoked" color="red" class="active-sessions-panel__tag">
                已撤销
              </a-tag>
              <a-tag color="default" class="active-sessions-panel__tag">
                {{ record.auth_provider }}
              </a-tag>
            </div>
            <div class="active-sessions-panel__meta">
              {{ record.ip || 'unknown IP' }}
            </div>
            <div v-if="adminMode && record.revoked_reason" class="active-sessions-panel__meta">
              撤销原因: {{ record.revoked_reason }}
            </div>
          </template>
          <template v-else-if="column.key === 'created_at'">
            {{ formatTime(record.created_at) }}
          </template>
          <template v-else-if="column.key === 'last_active_at'">
            {{ formatTime(record.last_active_at) }}
          </template>
          <template v-else-if="column.key === 'expires_at'">
            {{ formatTime(record.expires_at) }}
          </template>
          <template v-else-if="column.key === 'actions'">
            <a-popconfirm
              v-if="canRevoke(record)"
              title="确认要撤销这个会话吗？该设备将立即登出。"
              ok-text="撤销"
              cancel-text="取消"
              @confirm="handleRevokeClick(record.id)"
            >
              <a-button type="link" danger size="small" :loading="revokingId === record.id">
                撤销
              </a-button>
            </a-popconfirm>
            <span v-else class="active-sessions-panel__current-label">—</span>
          </template>
        </template>
        <template #emptyText>
          <div v-if="!effectiveLoading" class="active-sessions-panel__empty">没有活跃会话</div>
        </template>
      </a-table>
    </a-spin>

    <div v-if="!adminMode" class="active-sessions-panel__actions">
      <a-button
        type="default"
        :loading="revokingAll"
        :disabled="displaySessions.filter((s) => !s.is_current && !s.revoked).length === 0"
        @click="handleRevokeAllOthers"
      >
        撤销其他所有设备
      </a-button>
      <a-button type="link" @click="handleRefreshClick">刷新</a-button>
    </div>
    <div v-else class="active-sessions-panel__actions">
      <a-button type="link" @click="handleRefreshClick">刷新</a-button>
    </div>
  </div>
</template>

<script setup lang="ts">
import { ref, computed, onMounted } from 'vue'
import { message } from 'ant-design-vue'
import type { TableColumnType } from 'ant-design-vue'
import { useAuthStore, type ISessionInfo } from '@/stores/auth'

/**
 * The panel can be rendered in two modes:
 *
 * - default (self): lists the *current user's* sessions via
 *   ``/auth/sessions``. The panel auto-fetches on mount and after
 *   revoke events. Revoke is only available for non-current sessions
 *   of the current user. The "revoke all others" button is shown.
 *
 * - adminMode: lists *any user's* sessions. The ``sessions`` prop MUST
 *   be supplied by the caller (it knows the userId); the panel does
 *   not fetch on its own. Revoke is available for any non-revoked
 *   session, including the current one. The "revoke all others"
 *   button is hidden (admins operate one session at a time).
 *
 * The component remains a single source of truth for the table
 * layout; the consumer (self or admin) decides what to pass as props
 * and how to fetch the data.
 */
const props = withDefaults(
  defineProps<{
    /**
     * Session rows to render.  In ``adminMode`` this is required —
     * the caller passes the list it loaded for the target user.
     * In self mode, the prop is optional (the panel auto-fetches
     * its own data via ``/auth/sessions`` when the prop is omitted).
     */
    sessions?: ISessionInfo[]
    loading?: boolean
    revokingId?: string | null
    /**
     * When true, the panel renders the admin variant. Callers must
     * wire up their own revoke handler (see the ``revoke`` event)
     * because the panel does not know how to call admin endpoints.
     */
    adminMode?: boolean
  }>(),
  {
    sessions: () => [] as ISessionInfo[],
    loading: false,
    revokingId: null,
    adminMode: false,
  },
)

const emit = defineEmits<{
  (e: 'refresh'): void
  (e: 'revoke', sid: string): void
}>()

const authStore = useAuthStore()
const error = ref('')
const revokingAll = ref(false)
const _selfSessions = ref<ISessionInfo[]>([])
const _selfLoading = ref(false)

/** The list the table renders — admin uses props, self uses our own ref. */
const displaySessions = computed<ISessionInfo[]>(() =>
  props.adminMode ? props.sessions : _selfSessions.value,
)

/** Loading flag — admin uses props.loading, self uses our own ref. */
const effectiveLoading = computed<boolean>(() =>
  props.adminMode ? props.loading : _selfLoading.value,
)

const columns: TableColumnType<ISessionInfo>[] = [
  {
    title: '设备',
    key: 'device',
    width: 320,
  },
  {
    title: '创建时间',
    key: 'created_at',
    width: 180,
  },
  {
    title: '最近活动',
    key: 'last_active_at',
    width: 180,
  },
  {
    title: '过期时间',
    key: 'expires_at',
    width: 180,
  },
  {
    title: '操作',
    key: 'actions',
    width: 100,
    align: 'right',
  },
]

function formatTime(iso: string): string {
  if (!iso) return '—'
  try {
    const d = new Date(iso)
    return d.toLocaleString('zh-CN', {
      year: 'numeric',
      month: '2-digit',
      day: '2-digit',
      hour: '2-digit',
      minute: '2-digit',
    })
  } catch {
    return iso
  }
}

/** Whether the revoke button should be shown for a given row. */
function canRevoke(record: ISessionInfo): boolean {
  // Always hide revoke for already-revoked sessions (the row is for
  // audit; revoking again is a no-op).
  if (record.revoked) return false
  if (props.adminMode) {
    // In admin mode, every active session is revocable.
    return true
  }
  // In self mode, the current session cannot self-revoke (use Logout).
  return !record.is_current
}

/** Apply a strikethrough class to revoked rows. */
function rowClass(record: ISessionInfo): string {
  if (record.revoked) return 'active-sessions-panel__row active-sessions-panel__row--revoked'
  return 'active-sessions-panel__row'
}

/** Self-mode auto-fetch. */
async function _loadSelfSessions(): Promise<void> {
  if (props.adminMode) return
  _selfLoading.value = true
  try {
    _selfSessions.value = await authStore.listSessions()
  } catch (err) {
    error.value = _extractMessage(err, '加载会话列表失败')
  } finally {
    _selfLoading.value = false
  }
}

/** Unified click handler for the per-row revoke button. */
function handleRevokeClick(sid: string): void {
  if (props.adminMode) {
    // In admin mode the caller wires the actual API call.
    emit('revoke', sid)
    return
  }
  // Self mode: revoke via the auth store, then refresh.
  void _selfRevokeOne(sid)
}

async function _selfRevokeOne(sid: string): Promise<void> {
  try {
    await authStore.revokeSession(sid)
    message.success('已撤销该会话')
    await _loadSelfSessions()
  } catch (err) {
    message.error(_extractMessage(err, '撤销会话失败'))
  }
}

function handleRefreshClick(): void {
  if (props.adminMode) {
    emit('refresh')
    return
  }
  void _loadSelfSessions()
}

async function handleRevokeAllOthers(): Promise<void> {
  if (props.adminMode) return
  revokingAll.value = true
  try {
    // Revoke each non-current session sequentially.  This is
    // intentionally NOT a single bulk endpoint — the server's
    // /auth/sessions/{sid} is the only per-session revoke surface
    // available, and we want atomic per-session feedback.
    const others = displaySessions.value.filter((s) => !s.is_current && !s.revoked)
    for (const s of others) {
      try {
        await authStore.revokeSession(s.id)
      } catch (err) {
        // Continue revoking the rest even if one fails.
        console.error(`Failed to revoke session ${s.id}`, err)
      }
    }
    await _loadSelfSessions()
    message.success(`已撤销 ${others.length} 个其他设备`)
  } finally {
    revokingAll.value = false
  }
}

function _extractMessage(err: unknown, fallback: string): string {
  if (err && typeof err === 'object') {
    const obj = err as { message?: unknown; detail?: unknown }
    if (typeof obj.detail === 'string') return obj.detail
    if (typeof obj.message === 'string') return obj.message
  }
  return fallback
}

onMounted(() => {
  if (!props.adminMode) {
    void _loadSelfSessions()
  }
})

defineExpose({ error })
</script>

<style scoped>
.active-sessions-panel {
  max-width: 900px;
}

.active-sessions-panel__alert {
  margin-bottom: 16px;
}

.active-sessions-panel__device {
  display: flex;
  align-items: center;
  gap: 8px;
  font-weight: 500;
}

.active-sessions-panel__tag {
  margin-left: 0;
  font-size: 11px;
}

.active-sessions-panel__meta {
  font-size: 12px;
  color: var(--text-tertiary);
  margin-top: 2px;
}

.active-sessions-panel__current-label {
  color: var(--text-quaternary);
  font-style: italic;
}

.active-sessions-panel__empty {
  padding: 32px 0;
  text-align: center;
  color: var(--text-tertiary);
}

.active-sessions-panel__actions {
  margin-top: 16px;
  display: flex;
  align-items: center;
  gap: 12px;
}

/* When row is the current session, dim the row visually */
:deep(.active-sessions-panel__row td) {
  vertical-align: top;
}

:deep(.active-sessions-panel__row--revoked td) {
  text-decoration: line-through;
  color: var(--text-quaternary);
}
</style>
