import React, { createContext, useContext, useState, useCallback, useMemo, useEffect, useRef } from 'react';
import { PublicClientApplication, InteractionRequiredAuthError } from '@azure/msal-browser';
import { msalConfig, loginRequest } from './authConfig';

/**
 * AuthProvider for Entra External ID (Customer Identity / CIAM).
 *
 * Uses @azure/msal-browser v3 with popup flow.
 * Supports Google, Apple, and Microsoft via identity_provider_hint.
 *
 * Exposed context shape:
 *   isLoading       — true while MSAL initializes or a login is in progress
 *   isSignedIn      — true when a user is authenticated
 *   user            — { entraSubjectId, email, displayName, firstName, surname, provider }
 *   error           — string | null
 *   signIn(hint)    — initiates login popup for 'google' | 'apple' | 'microsoft'
 *   signOut()       — clears session
 *   getAccessToken()— returns a fresh ID token for authenticating API calls
 *   clearError()    — clears the error state
 */

// ── Provider hint → Entra External ID identity_provider_hint ─────────────
// Values must match the social IdP names configured in the CIAM tenant.
const PROVIDER_HINTS = {
    google:    'google',
    apple:     'apple.com',
    microsoft: undefined,   // No hint = email/password or MSA
};

// ── MSAL instance — module-level singleton ────────────────────────────────
const msalInstance = new PublicClientApplication(msalConfig);

// ── Context ───────────────────────────────────────────────────────────────
const AuthContext = createContext(null);

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

// ── Map ID token claims → user object ─────────────────────────────────────
function claimsToUser(claims, provider) {
    return {
        entraSubjectId: claims.oid ?? claims.sub ?? null,
        email:          claims.email ?? claims.preferred_username ?? null,
        displayName:    claims.name ?? null,
        firstName:      claims.given_name ?? null,
        surname:        claims.family_name ?? null,
        provider:       provider ?? 'unknown',
    };
}

// ── Provider ──────────────────────────────────────────────────────────────
export function AuthProvider({ children }) {
    const [isLoading, setIsLoading] = useState(true);
    const [user,      setUser]      = useState(null);
    const [error,     setError]     = useState(null);
    const initialized = useRef(false);

    const isSignedIn = !!user;

    // ── Initialize MSAL + handle redirect response on mount ───────────
    useEffect(() => {
        if (initialized.current) return;
        initialized.current = true;

        (async () => {
            try {
                await msalInstance.initialize();

                // Handle returning from loginRedirect
                const redirectResult = await msalInstance.handleRedirectPromise();
                if (redirectResult?.idTokenClaims) {
                    const provider = sessionStorage.getItem('adp_pending_provider') ?? 'unknown';
                    sessionStorage.removeItem('adp_pending_provider');
                    setUser(claimsToUser(redirectResult.idTokenClaims, provider));
                    setIsLoading(false);
                    return;
                }

                // Restore existing cached account
                const accounts = msalInstance.getAllAccounts();
                if (accounts.length > 0) {
                    try {
                        const silent = await msalInstance.acquireTokenSilent({
                            ...loginRequest,
                            account: accounts[0],
                        });
                        if (silent?.idTokenClaims) {
                            const idp = accounts[0].idTokenClaims?.idp ?? 'unknown';
                            setUser(claimsToUser(silent.idTokenClaims, idp));
                        }
                    } catch (silentErr) {
                        if (silentErr instanceof InteractionRequiredAuthError) {
                            await msalInstance.clearCache();
                        } else {
                            console.warn('[Auth] Silent refresh failed:', silentErr.message);
                        }
                    }
                }
            } catch (err) {
                console.error('[Auth] MSAL init error:', err);
                setError('Authentication service unavailable. Please refresh the page.');
            } finally {
                setIsLoading(false);
            }
        })();
    }, []);

    // ── Sign in ────────────────────────────────────────────────────────
    const signIn = useCallback(async (providerHint) => {
        setIsLoading(true);
        setError(null);

        const hint = PROVIDER_HINTS[providerHint];
        const request = {
            ...loginRequest,
            ...(hint && { extraQueryParameters: { identity_provider_hint: hint } }),
        };

        try {
            const result = await msalInstance.loginPopup(request);
            if (result?.idTokenClaims) {
                setUser(claimsToUser(result.idTokenClaims, providerHint));
            }
        } catch (popupErr) {
            const msg = popupErr?.message ?? '';

            if (msg.includes('popup_window_error')) {
                try {
                    sessionStorage.setItem('adp_pending_provider', providerHint ?? 'unknown');
                    await msalInstance.loginRedirect(request);
                    return;
                } catch (redirectErr) {
                    console.error('[Auth] Redirect fallback failed:', redirectErr);
                    setError('Sign-in failed. Please try again.');
                }
            } else if (msg.includes('user_cancelled') || msg.includes('access_denied')) {
                setError(null);
            } else {
                console.error('[Auth] Sign-in error:', popupErr);
                setError('Sign-in failed. Please try again.');
            }
        } finally {
            setIsLoading(false);
        }
    }, []);

    // ── Get access token for API calls ────────────────────────────────
    // Returns the ID token — used as Bearer token when calling the
    // Registration Function. The Function validates it against the CIAM
    // tenant and extracts the OID claim as the user's identity.
    const getAccessToken = useCallback(async () => {
        const accounts = msalInstance.getAllAccounts();
        if (accounts.length === 0) throw new Error('Not signed in');

        try {
            const result = await msalInstance.acquireTokenSilent({
                ...loginRequest,
                account: accounts[0],
            });
            return result.idToken;
        } catch (err) {
            if (err instanceof InteractionRequiredAuthError) {
                throw new Error('Session expired. Please sign in again.');
            }
            throw err;
        }
    }, []);

    // ── Sign out ───────────────────────────────────────────────────────
    const signOut = useCallback(async () => {
        const accounts = msalInstance.getAllAccounts();
        setUser(null);
        try {
            if (accounts.length > 0) {
                await msalInstance.logoutPopup({ account: accounts[0] });
            }
        } catch {
            // Local state already cleared
        }
    }, []);

    const clearError = useCallback(() => setError(null), []);

    const value = useMemo(() => ({
        isLoading,
        isSignedIn,
        user,
        error,
        signIn,
        signOut,
        getAccessToken,
        clearError,
    }), [isLoading, isSignedIn, user, error, signIn, signOut, getAccessToken, clearError]);

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