import {
  createContext,
  useCallback,
  useContext,
  useEffect,
  useMemo,
  useState,
  type ReactNode,
} from 'react'
import { ApiError, getAccessToken, isAbortError } from '../lib/api'
import {
  setActiveBusinessById,
} from '../lib/businessContext'
import { getBusinessDomain } from '../lib/config'
import {
  fetchCurrentUser,
  login as loginRequest,
  loginWithOtp as loginWithOtpRequest,
  logout as logoutRequest,
} from '../services/authService'
import { resolveTenantByDomain } from '../services/tenantService'
import type { AuthUser } from '../types/auth'

interface AuthContextValue {
  user: AuthUser | null
  isLoading: boolean
  login: (cellNumber: string, password: string) => Promise<void>
  loginWithOtp: (cellNumber: string, code: string) => Promise<void>
  logout: () => void
  setUser: (user: AuthUser) => void
}

const AuthContext = createContext<AuthContextValue | null>(null)

const BUSINESS_ACCESS_MESSAGE =
  'This account cannot access the business dashboard. Only business owners and staff can sign in here.'

function isSuperAdminUser(user: AuthUser) {
  return user.isSuperAdmin === true || user.roles.includes('super_admin')
}

function canAccessBusinessDashboard(user: AuthUser) {
  if (isSuperAdminUser(user)) {
    return true
  }

  return user.dashboard === 'business' && user.businesses.length > 0
}

async function prepareBusinessContext(user: AuthUser) {
  const domain = getBusinessDomain()

  if (isSuperAdminUser(user)) {
    const tenant = await resolveTenantByDomain(domain)
    setActiveBusinessById(String(tenant.id), tenant.domain)
    return
  }

  const membership =
    user.businesses.find((business) => business.slug === tenantSlugFromDomain(domain)) ??
    user.businesses[0]

  if (!membership) {
    throw new ApiError('No business membership found for this account.', 403)
  }

  setActiveBusinessById(String(membership.id), domain)
}

function tenantSlugFromDomain(domain: string) {
  return domain.split('.')[0]?.toLowerCase() ?? domain
}

async function assertBusinessAccess(user: AuthUser) {
  if (!canAccessBusinessDashboard(user)) {
    logoutRequest()
    throw new ApiError(BUSINESS_ACCESS_MESSAGE, 403)
  }

  await prepareBusinessContext(user)
}

export function AuthProvider({ children }: { children: ReactNode }) {
  const [user, setUser] = useState<AuthUser | null>(null)
  const [isLoading, setIsLoading] = useState(true)

  const logout = useCallback(() => {
    logoutRequest()
    setUser(null)
  }, [])

  useEffect(() => {
    const controller = new AbortController()

    async function init() {
      if (!getAccessToken()) {
        if (!controller.signal.aborted) setIsLoading(false)
        return
      }

      try {
        const { user: currentUser } = await fetchCurrentUser(controller.signal)
        if (controller.signal.aborted) return

        if (!canAccessBusinessDashboard(currentUser)) {
          logout()
          return
        }

        await prepareBusinessContext(currentUser)
        if (controller.signal.aborted) return

        setUser(currentUser)
      } catch (err) {
        if (isAbortError(err) || controller.signal.aborted) return
        logout()
      } finally {
        if (!controller.signal.aborted) setIsLoading(false)
      }
    }

    void init()

    return () => {
      controller.abort()
    }
  }, [logout])

  const login = useCallback(async (cellNumber: string, password: string) => {
    const data = await loginRequest(cellNumber, password)
    await assertBusinessAccess(data.user)
    setUser(data.user)
  }, [])

  const loginWithOtp = useCallback(async (cellNumber: string, code: string) => {
    const data = await loginWithOtpRequest(cellNumber, code)
    await assertBusinessAccess(data.user)
    setUser(data.user)
  }, [])

  const value = useMemo(
    () => ({ user, isLoading, login, loginWithOtp, logout, setUser }),
    [user, isLoading, login, loginWithOtp, logout],
  )

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

export function useAuth() {
  const context = useContext(AuthContext)
  if (!context) {
    throw new Error('useAuth must be used within AuthProvider')
  }
  return context
}

export { BUSINESS_ACCESS_MESSAGE }
