import React from 'react';
import { useQueryClient } from '@tanstack/react-query';
import {
  authorizedFetch,
  clearTokens,
  isAuthError,
  loadToken,
  registerAuthFailureHandler,
  storePersonalAccessToken,
} from './tokens';
import { invalidateTenantApiCache } from '../api';

export type AuthStatus = 'loading' | 'authenticated' | 'unauthenticated';

export interface TenantProfile {
  id: number;
  tenant_id: number;
  role?: string | null;
  name?: string;
  slug?: string;
  email?: string | null;
  features?: Record<string, unknown>;
  [key: string]: unknown;
}

interface AuthContextValue {
  status: AuthStatus;
  user: TenantProfile | null;
  abilities: string[];
  hasAbility: (ability: string) => boolean;
  refreshProfile: () => Promise<void>;
  logout: (options?: { redirect?: string }) => Promise<void>;
  applyToken: (token: string, abilities: string[]) => Promise<void>;
}

const AuthContext = React.createContext<AuthContextValue | undefined>(undefined);

function getCsrfToken(): string | undefined {
  const meta = document.querySelector('meta[name="csrf-token"]') as HTMLMetaElement | null;
  return meta?.content;
}

async function exchangeSessionForToken(): Promise<{ token: string; abilities: string[] } | null> {
  const csrf = getCsrfToken();

  try {
    const response = await fetch('/api/v1/tenant-auth/exchange', {
      method: 'POST',
      headers: {
        'Accept': 'application/json',
        'X-Requested-With': 'XMLHttpRequest',
        ...(csrf ? { 'X-CSRF-TOKEN': csrf } : {}),
      },
      credentials: 'same-origin',
    });

    if (response.status === 204) {
      return null;
    }

    if (!response.ok) {
      return null;
    }

    const data = (await response.json()) as { token: string; abilities?: string[] };

    if (!data?.token) {
      return null;
    }

    return {
      token: data.token,
      abilities: Array.isArray(data.abilities) ? data.abilities : [],
    };
  } catch (error) {
    if (import.meta.env.DEV) {
      console.warn('[Auth] Session exchange failed', error);
    }
    return null;
  }
}

export const AuthProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => {
  const [status, setStatus] = React.useState<AuthStatus>('loading');
  const [user, setUser] = React.useState<TenantProfile | null>(null);
  const [abilities, setAbilities] = React.useState<string[]>([]);
  const queryClient = useQueryClient();
  const profileQueryKey = React.useMemo(() => ['tenantProfile'], []);

  const handleAuthFailure = React.useCallback(() => {
    clearTokens();
    invalidateTenantApiCache();
    queryClient.removeQueries({ queryKey: profileQueryKey });
    setUser(null);
    setAbilities([]);
    setStatus('unauthenticated');
  }, [profileQueryKey, queryClient]);

  React.useEffect(() => {
    const unsubscribe = registerAuthFailureHandler(handleAuthFailure);
    return unsubscribe;
  }, [handleAuthFailure]);

  const refreshProfile = React.useCallback(async () => {
    setStatus('loading');

    try {
      const data = await queryClient.fetchQuery({
        queryKey: profileQueryKey,
        queryFn: async () => {
          const response = await authorizedFetch('/api/v1/tenant-auth/me', {
            method: 'GET',
          });

          if (!response.ok) {
            throw new Error('Failed to fetch tenant profile');
          }

          return (await response.json()) as {
            user: TenantProfile | null;
            tenant?: Record<string, unknown> | null;
            abilities: string[];
          };
        },
        staleTime: 1000 * 60 * 5,
        retry: 1,
      });

      const composed: TenantProfile | null = data.user && data.tenant
        ? { ...data.user, ...data.tenant }
        : data.user;

      setUser(composed ?? null);
      setAbilities(Array.isArray(data?.abilities) ? data.abilities : []);
      setStatus('authenticated');
    } catch (error) {
      console.error('[Auth] Failed to refresh profile', error);

      if (isAuthError(error)) {
        handleAuthFailure();
      } else {
        setStatus('unauthenticated');
      }

      throw error;
    }
  }, [handleAuthFailure, profileQueryKey, queryClient]);

  const applyToken = React.useCallback(async (token: string, tokenAbilities: string[]) => {
    storePersonalAccessToken(token, tokenAbilities);
    setAbilities(tokenAbilities);
    await refreshProfile();
  }, [refreshProfile]);

  React.useEffect(() => {
    let cancelled = false;

    const bootstrap = async () => {
      const stored = loadToken();
      if (stored) {
        try {
          await refreshProfile();
          return;
        } catch (error) {
          if (import.meta.env.DEV) {
            console.warn('[Auth] Stored token bootstrap failed', error);
          }
        }
      }

      const exchanged = await exchangeSessionForToken();
      if (cancelled) {
        return;
      }

      if (exchanged) {
        await applyToken(exchanged.token, exchanged.abilities);
        return;
      }

      handleAuthFailure();
    };

    bootstrap().catch((error) => {
      if (import.meta.env.DEV) {
        console.error('[Auth] Failed to bootstrap authentication', error);
      }
      handleAuthFailure();
    });

    return () => {
      cancelled = true;
    };
  }, [applyToken, handleAuthFailure, refreshProfile]);

  const logout = React.useCallback(async ({ redirect }: { redirect?: string } = {}) => {
    try {
      await authorizedFetch('/api/v1/tenant-auth/logout', {
        method: 'POST',
      });
    } catch (error) {
      if (import.meta.env.DEV) {
        console.warn('[Auth] API logout failed', error);
      }
    } finally {
      try {
        const csrf = getCsrfToken();
        await fetch('/logout', {
          method: 'POST',
          headers: {
            'X-Requested-With': 'XMLHttpRequest',
            ...(csrf ? { 'X-CSRF-TOKEN': csrf } : {}),
          },
          credentials: 'same-origin',
        });
      } catch (error) {
        if (import.meta.env.DEV) {
          console.warn('[Auth] Session logout failed', error);
        }
      }

      handleAuthFailure();

      if (redirect) {
        window.location.href = redirect;
      }
    }
  }, [handleAuthFailure]);

  const hasAbility = React.useCallback((ability: string) => abilities.includes(ability), [abilities]);

  const value = React.useMemo<AuthContextValue>(
    () => ({ status, user, abilities, hasAbility, refreshProfile, logout, applyToken }),
    [status, user, abilities, hasAbility, refreshProfile, logout, applyToken]
  );

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

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