"use client"

import { createContext, useContext, useState, useCallback, useEffect, useMemo, useRef } from "react"
import { hasConsoleScopes, type ConsolePolicy } from "@/lib/console-policy-parser"
import { hasConsoleCapability, type ConsoleCapability } from "@/lib/permission-capabilities"
import type { PermissionResourceContext } from "@/lib/permission-resources"
import { CONSOLE_SCOPES, PAGE_PERMISSIONS } from "@/lib/console-permissions"
import { scheduleMicrotask } from "@/lib/schedule-microtask"
import { useAuth } from "@/contexts/auth-context"
import { useApiOptional } from "@/contexts/api-context"

interface PermissionsContextValue {
  userPolicy: ConsolePolicy | null
  userInfo: Record<string, unknown> | null
  isLoading: boolean
  hasResolvedAdmin: boolean
  hasFetchedPolicy: boolean
  fetchUserPolicy: () => Promise<void>
  hasPermission: (action: string | string[], matchAll?: boolean) => boolean
  canCapability: (capability: ConsoleCapability, context?: PermissionResourceContext) => boolean
  canAccessPath: (path: string) => boolean
  isAdmin: boolean
  /** True when user has consoleAdmin (or is admin). Only these users can change password per RustFS backend. */
  canChangePassword: boolean
}

const PermissionsContext = createContext<PermissionsContextValue | null>(null)

export function PermissionsProvider({ children }: { children: React.ReactNode }) {
  const api = useApiOptional()
  const { isAdmin, isAuthenticated, credentials, setIsAdmin } = useAuth()

  const [userPolicy, setUserPolicy] = useState<ConsolePolicy | null>(null)
  const [userInfo, setUserInfo] = useState<Record<string, unknown> | null>(null)
  const [isLoading, setIsLoading] = useState(false)
  const [hasResolvedAdmin, setHasResolvedAdmin] = useState(false)
  const [hasFetchedPolicy, setHasFetchedPolicy] = useState(false)
  const requestEpochRef = useRef(0)

  useEffect(() => {
    requestEpochRef.current += 1
    setUserInfo(null)
    setUserPolicy(null)
    setHasResolvedAdmin(false)
    setHasFetchedPolicy(false)
    if (isAuthenticated) {
      setIsAdmin(false)
    }
  }, [api, credentials?.AccessKeyId, credentials?.SessionToken, isAuthenticated, setIsAdmin])

  const fetchAdminStatus = useCallback(async () => {
    if (!api) return
    const requestEpoch = requestEpochRef.current

    try {
      const info = (await api.get("/is-admin")) as { is_admin?: boolean }
      if (requestEpoch !== requestEpochRef.current) return
      setIsAdmin(info?.is_admin ?? false)
    } catch (e) {
      if (requestEpoch !== requestEpochRef.current) return
      console.error("Failed to resolve admin status", e)
      setIsAdmin(false)
    } finally {
      if (requestEpoch === requestEpochRef.current) setHasResolvedAdmin(true)
    }
  }, [api, setIsAdmin])

  const fetchUserPolicy = useCallback(async () => {
    if (!api) return
    const requestEpoch = requestEpochRef.current

    setIsLoading(true)
    try {
      const info = (await api.get("/accountinfo")) as Record<string, unknown>
      if (requestEpoch !== requestEpochRef.current) return
      setUserInfo(info)
      const policyStr = info?.policy as string
      if (policyStr) {
        setUserPolicy(JSON.parse(policyStr) as ConsolePolicy)
      } else {
        setUserPolicy(null)
      }
    } catch (e) {
      if (requestEpoch !== requestEpochRef.current) return
      console.error("Failed to fetch user policy", e)
      setUserPolicy(null)
    } finally {
      if (requestEpoch === requestEpochRef.current) {
        setIsLoading(false)
        setHasFetchedPolicy(true)
      }
    }
  }, [api])

  const hasPermission = useCallback(
    (action: string | string[], matchAll: boolean = true) => {
      if (isAdmin) return true
      if (!userPolicy) return false

      const actions = Array.isArray(action) ? action : [action]
      return hasConsoleScopes(userPolicy, actions, matchAll)
    },
    [isAdmin, userPolicy],
  )

  const canCapability = useCallback(
    (capability: ConsoleCapability, context: PermissionResourceContext = {}) => {
      if (isAdmin) return true
      return hasConsoleCapability(userPolicy, capability, context)
    },
    [isAdmin, userPolicy],
  )

  const canAccessPath = useCallback(
    (path: string) => {
      if (isAdmin) return true

      let requiredScopes = PAGE_PERMISSIONS[path]
      if (!requiredScopes) {
        const match = Object.keys(PAGE_PERMISSIONS).find((key) => path.startsWith(key))
        if (match) {
          requiredScopes = PAGE_PERMISSIONS[match]
        }
      }

      if (!requiredScopes) return true

      return hasPermission(requiredScopes)
    },
    [isAdmin, hasPermission],
  )

  useEffect(() => {
    let cancelled = false
    if (api && isAuthenticated && !hasResolvedAdmin) {
      scheduleMicrotask(() => {
        if (!cancelled) void fetchAdminStatus()
      })
    }

    return () => {
      cancelled = true
    }
  }, [api, isAuthenticated, hasResolvedAdmin, fetchAdminStatus])

  useEffect(() => {
    if (api && isAuthenticated && hasResolvedAdmin && !hasFetchedPolicy && !isLoading) {
      fetchUserPolicy()
    }
  }, [api, isAuthenticated, hasResolvedAdmin, hasFetchedPolicy, isLoading, fetchUserPolicy])

  const canChangePassword = isAdmin || hasPermission(CONSOLE_SCOPES.CONSOLE_ADMIN)

  const value = useMemo(
    () => ({
      userPolicy,
      userInfo,
      isLoading,
      hasResolvedAdmin,
      hasFetchedPolicy,
      fetchUserPolicy,
      hasPermission,
      canCapability,
      canAccessPath,
      isAdmin,
      canChangePassword,
    }),
    [
      userPolicy,
      userInfo,
      isLoading,
      hasResolvedAdmin,
      hasFetchedPolicy,
      fetchUserPolicy,
      hasPermission,
      canCapability,
      canAccessPath,
      isAdmin,
      canChangePassword,
    ],
  )

  return <PermissionsContext.Provider value={value}>{children}</PermissionsContext.Provider>
}

export function usePermissions() {
  const ctx = useContext(PermissionsContext)
  if (!ctx) {
    throw new Error("usePermissions must be used within PermissionsProvider")
  }
  return ctx
}
