'use client'

import {
    createContext,
    useContext,
    useState,
    useEffect,
    useCallback,
    useRef,
    type ReactNode,
} from 'react'
import type { JWTTokens, JWTConfig, JWTState } from '../client/types'
import { getCSRFToken } from '../utils'

const Context = createContext<JWTState | null>(null)

const DEFAULT_CONFIG: Required<JWTConfig> = {
    baseUrl: '',
    endpoint: '/api/mizan/call/',
    refreshBuffer: 30,
    autoObtain: true,
    autoRefresh: true,
}

interface JWTContextProps {
    children: ReactNode
    config?: JWTConfig
}

export function JWTContext({ children, config }: JWTContextProps) {
    const cfg = { ...DEFAULT_CONFIG, ...config }

    const [tokens, setTokens] = useState<JWTTokens | null>(null)
    const [isLoading, setIsLoading] = useState(false)
    const [error, setError] = useState<Error | null>(null)

    const refreshTimeoutRef = useRef<NodeJS.Timeout | null>(null)

    // Helper to call server functions
    const callServerFunction = useCallback(async (fn: string, args: Record<string, unknown> = {}) => {
        const url = cfg.baseUrl ? `${cfg.baseUrl}${cfg.endpoint}` : cfg.endpoint
        const csrfToken = getCSRFToken()
        const response = await fetch(url, {
            method: 'POST',
            headers: {
                'Content-Type': 'application/json',
                ...(csrfToken ? { 'X-CSRFToken': csrfToken } : {}),
            },
            credentials: 'include', // Include session cookie for CSRF
            body: JSON.stringify({ fn, args }),
        })

        const data = await response.json()

        if (data.error) {
            const err = new Error(data.message || 'Server function failed')
            ;(err as any).code = data.code
            ;(err as any).details = data.details
            throw err
        }

        return data.data
    }, [cfg.baseUrl, cfg.endpoint])

    // Obtain tokens from session
    const obtainTokens = useCallback(async (): Promise<JWTTokens | null> => {
        setIsLoading(true)
        setError(null)

        try {
            const result = await callServerFunction('jwt_obtain')
            const newTokens: JWTTokens = {
                accessToken: result.access_token,
                refreshToken: result.refresh_token,
                expiresAt: Date.now() + result.expires_in * 1000,
            }

            setTokens(newTokens)
            return newTokens
        } catch (err: any) {
            // FORBIDDEN means not authenticated - expected, not an error
            if (err.code === 'FORBIDDEN') {
                setTokens(null)
                return null
            }
            const error = err instanceof Error ? err : new Error(String(err))
            setError(error)
            return null
        } finally {
            setIsLoading(false)
        }
    }, [callServerFunction])

    // Refresh tokens
    const refreshTokens = useCallback(async (): Promise<JWTTokens | null> => {
        if (!tokens?.refreshToken) {
            return null
        }

        setIsLoading(true)
        setError(null)

        try {
            const result = await callServerFunction('jwt_refresh', {
                refresh_token: tokens.refreshToken,
            })
            const newTokens: JWTTokens = {
                accessToken: result.access_token,
                refreshToken: result.refresh_token,
                expiresAt: Date.now() + result.expires_in * 1000,
            }

            setTokens(newTokens)
            return newTokens
        } catch (err: any) {
            // FORBIDDEN means refresh token invalid/expired - clear tokens
            if (err.code === 'FORBIDDEN') {
                setTokens(null)
                return null
            }
            const error = err instanceof Error ? err : new Error(String(err))
            setError(error)
            return null
        } finally {
            setIsLoading(false)
        }
    }, [tokens?.refreshToken, callServerFunction])

    // Clear tokens
    const clearTokens = useCallback(() => {
        setTokens(null)
        setError(null)
        if (refreshTimeoutRef.current) {
            clearTimeout(refreshTimeoutRef.current)
            refreshTimeoutRef.current = null
        }
    }, [])

    // Get access token (refresh if needed)
    const getAccessToken = useCallback(async (): Promise<string | null> => {
        if (!tokens) {
            return null
        }

        // Check if token needs refresh
        const bufferMs = cfg.refreshBuffer * 1000
        if (tokens.expiresAt - Date.now() < bufferMs) {
            const newTokens = await refreshTokens()
            return newTokens?.accessToken ?? null
        }

        return tokens.accessToken
    }, [tokens, cfg.refreshBuffer, refreshTokens])

    // Auto-obtain on mount
    useEffect(() => {
        if (!cfg.autoObtain) return
        obtainTokens()
    }, [cfg.autoObtain, obtainTokens])

    // Auto-refresh before expiry
    useEffect(() => {
        if (!cfg.autoRefresh || !tokens) {
            return
        }

        // Clear existing timeout
        if (refreshTimeoutRef.current) {
            clearTimeout(refreshTimeoutRef.current)
        }

        // Schedule refresh
        const bufferMs = cfg.refreshBuffer * 1000
        const timeUntilRefresh = tokens.expiresAt - Date.now() - bufferMs

        if (timeUntilRefresh > 0) {
            refreshTimeoutRef.current = setTimeout(() => {
                refreshTokens()
            }, timeUntilRefresh)
        }

        return () => {
            if (refreshTimeoutRef.current) {
                clearTimeout(refreshTimeoutRef.current)
            }
        }
    }, [cfg.autoRefresh, cfg.refreshBuffer, tokens, refreshTokens])

    const value: JWTState = {
        tokens,
        isLoading,
        error,
        obtainTokens,
        refreshTokens,
        clearTokens,
        getAccessToken,
    }

    return <Context value={value}>{children}</Context>
}

/**
 * Hook to access JWT state and methods.
 *
 * When used outside JWTContext, returns null. This allows
 * conditional JWT usage (e.g., useDjangoApi({ jwt: true }))
 * without requiring JWTContext to always be present.
 */
export function useJWT(): JWTState | null {
    return useContext(Context)
}

/**
 * Hook to access JWT state with a guarantee it exists.
 * Throws if used outside JWTContext.
 */
export function useJWTRequired(): JWTState {
    const context = useContext(Context)
    if (!context) {
        throw new Error('useJWTRequired must be used within JWTContext')
    }
    return context
}

/** Check if JWT is available (tokens obtained) */
export function useJWTReady(): boolean {
    const jwt = useJWT()
    if (!jwt) return false
    return !jwt.isLoading && jwt.tokens !== null
}
