import { createContext, createSignal, useContext, onMount, Accessor, JSX } from 'solid-js';
import { Navigate } from '@solidjs/router';
import { getClerk, loadClerk, getClerkUrls } from './clerk-client';
import { User, UserRole, AuthState } from './types';
import { setAuthToken } from '../api/trpc-client';

interface ClerkUser {
  id: string;
  primaryEmailAddress?: {
    emailAddress: string;
  } | null;
  firstName?: string | null;
  lastName?: string | null;
  imageUrl: string;
  publicMetadata?: {
    role?: UserRole;
  };
  update: (data: any) => Promise<any>;
}

interface ClerkSession {
  getId: () => string;
  getUser: () => ClerkUser;
  getToken: (template?: string) => Promise<string | null>;
}

// Clerk's addListener emits resource instances, not typed events.
// We model the possible event shapes for type-safe handling.
type ClerkListenerEvent =
  | { __type: 'userSignedIn'; user: object }
  | { __type: 'userSignedOut' }
  | { __type: 'sessionChanged'; session: object }
  | { __type: 'userUpdated'; user: object };

function isClerkUserEvent(event: unknown): event is { user: ClerkUser } {
  if (event && typeof event === 'object' && 'user' in event) {
    return true;
  }
  return false;
}

function isClerkSignOutEvent(event: unknown): boolean {
  if (event && typeof event === 'object') {
    const e = event as Record<string, unknown>;
    return e.__type === 'userSignedOut' || e.__type === 'sessionRemoved';
  }
  return false;
}

interface ClerkClient {
  user: () => ClerkUser | undefined;
  session: () => ClerkSession | null;
  isLoading: boolean;
  signOut: () => Promise<void>;
  addListener: (handler: (event: unknown) => void) => void;
}

const AuthContext = createContext<Accessor<AuthState> | undefined>(undefined);
const AuthActionsContext = createContext<{
  signIn: () => void;
  signOut: () => Promise<void>;
  updateUser: (data: Partial<User>) => Promise<void>;
  clerkClient: Accessor<ClerkClient | null>;
} | undefined>(undefined);

export { AuthContext, AuthActionsContext };

function clerkUserToUser(clerkUser: any): User {
  const primaryEmail = clerkUser.primaryEmailAddress?.emailAddress || '';
  const firstName = clerkUser.firstName || '';
  const lastName = clerkUser.lastName || '';
  const name = [firstName, lastName].filter(Boolean).join(' ') || primaryEmail.split('@')[0] || 'User';
  
  const roleFromMetadata = (clerkUser.publicMetadata?.role as UserRole) || 'viewer';

  return {
    id: clerkUser.id,
    email: primaryEmail,
    name,
    avatarUrl: clerkUser.imageUrl,
    role: roleFromMetadata,
  };
}

export function ClerkProvider(props: { children: JSX.Element }) {
  const [state, setState] = createSignal<AuthState>({
    user: null,
    isLoading: true,
    isAuthenticated: false,
    error: null,
  });

  const [clerkClient, setClerkClient] = createSignal<ClerkClient | null>(null);

  onMount(async () => {
    try {
      const client = await loadClerk();
      if (!client) {
        setState({
          user: null,
          isLoading: false,
          isAuthenticated: false,
          error: 'Authentication service unavailable',
        });
        return;
      }

      const wrappedClient: ClerkClient = {
        user: () => client.user || undefined,
        session: () => (client.session as unknown as ClerkSession) || null,
        isLoading: false,
        signOut: async () => {
          await client.signOut();
          setState({
            user: null,
            isLoading: false,
            isAuthenticated: false,
            error: null,
          });
        },
        addListener: (handler: (event: unknown) => void) => {
          client.addListener((event: unknown) => handler(event));
        },
      };

      setClerkClient(wrappedClient);

      if (client.user) {
        const session = client.session as unknown as ClerkSession;
        if (session) {
          const token = await session.getToken();
          if (token) {
            setAuthToken(token);
          }
        }
        
        setState({
          user: clerkUserToUser(client.user),
          isLoading: false,
          isAuthenticated: true,
          error: null,
        });
      } else {
        setState((prev) => ({ ...prev, isLoading: false }));
        setAuthToken(null);
      }

      client.addListener((event: unknown) => {
        if (isClerkUserEvent(event) && event.user) {
          setState({
            user: clerkUserToUser(event.user),
            isLoading: false,
            isAuthenticated: true,
            error: null,
          });
        } else if (isClerkSignOutEvent(event)) {
          setState({
            user: null,
            isLoading: false,
            isAuthenticated: false,
            error: null,
          });
        }
      });
    } catch (err) {
      setState({
        user: null,
        isLoading: false,
        isAuthenticated: false,
        error: err instanceof Error ? err.message : 'Failed to initialize auth',
      });
    }
  });

  const signIn = () => {
    const urls = getClerkUrls();
    window.location.href = urls.signInUrl;
  };

  const signOut = async () => {
    const client = getClerk();
    if (client) {
      await client.signOut();
    }
    setAuthToken(null);
    setState({
      user: null,
      isLoading: false,
      isAuthenticated: false,
      error: null,
    });
  };

  const updateUser = async (data: Partial<User>) => {
    const client = getClerk();
    if (!client?.user) {
      throw new Error('Not authenticated');
    }

    const clerkUser = client.user;
    const updates: any = {};
    
    if (data.name) {
      const [firstName, ...lastNameParts] = data.name.split(' ');
      updates.firstName = firstName;
      updates.lastName = lastNameParts.join(' ');
    }
    
    await clerkUser.update(updates);
    
    setState((prev) => ({
      ...prev,
      user: prev.user ? { ...prev.user, ...data } : null,
    }));
  };

  return (
    <AuthContext.Provider value={state}>
      <AuthActionsContext.Provider value={{ signIn, signOut, updateUser, clerkClient }}>
        {props.children}
      </AuthActionsContext.Provider>
    </AuthContext.Provider>
  );
}

export function useAuth(): Accessor<AuthState> {
  const context = useContext(AuthContext);
  if (!context) {
    throw new Error('useAuth must be used within a ClerkProvider');
  }
  return context;
}

export function useAuthActions() {
  const context = useContext(AuthActionsContext);
  if (!context) {
    throw new Error('useAuthActions must be used within a ClerkProvider');
  }
  return context;
}

export function RequireAuth(props: { children: JSX.Element }) {
  const auth = useAuth();
  const authState = auth();

  if (authState.isLoading) {
    return <Navigate href="/sign-in" />;
  }

  if (!authState.isAuthenticated) {
    return <Navigate href="/sign-in" />;
  }

  return props.children;
}
