import {
  createContext,
  createEffect,
  createSignal,
  ParentComponent,
  useContext,
} from "solid-js";
import { createAuthClient } from "@neondatabase/neon-js/auth";
import type { AuthSession } from "../lib/types";

const neonAuthUrl = import.meta.env.VITE_NEON_AUTH_URL ?? "";
const authClient = neonAuthUrl ? createAuthClient(neonAuthUrl) : null;
const localAuthTokenKey = "bookra.localAuthToken";

type LocalUser = {
  id: string;
  email?: string;
  name?: string;
};

function sessionFromLocalToken(token: string, user?: LocalUser): AuthSession {
  const payload = parseJwtPayload(token);
  const expiresAt = typeof payload.exp === "number" ? new Date(payload.exp * 1000) : undefined;
  return {
    session: {
      id: token,
      userId: String(payload.sub ?? user?.id ?? ""),
      token,
      expiresAt,
    },
    user: {
      id: String(payload.sub ?? user?.id ?? ""),
      email: String(payload.email ?? user?.email ?? ""),
      name: String(payload.name ?? user?.name ?? ""),
      image: null,
    },
  };
}

function parseJwtPayload(token: string): Record<string, unknown> {
  try {
    const payload = token.split(".")[1];
    if (!payload) return {};
    const padded = payload.replace(/-/g, "+").replace(/_/g, "/").padEnd(Math.ceil(payload.length / 4) * 4, "=");
    return JSON.parse(atob(padded)) as Record<string, unknown>;
  } catch {
    return {};
  }
}

type AuthContextValue = {
  session: () => AuthSession | null;
  loading: () => boolean;
  usesNeonAuth: () => boolean;
  supportsMagicLink: () => boolean;
  supportsGoogleSignIn: () => boolean;
  getToken: () => Promise<string | null>;
  signUpWithEmail: (name: string, email: string, password: string) => Promise<void>;
  signInWithEmail: (email: string, password: string) => Promise<void>;
  signInAsDemo: () => Promise<void>;
  sendMagicLink: (email: string) => Promise<void>;
  signInWithMagicLink: (token: string, refreshToken?: string) => Promise<void>;
  signInWithGoogle: () => Promise<void>;
  signOut: () => Promise<void>;
};

const AuthContext = createContext<AuthContextValue>();

export const AuthProvider: ParentComponent = (props) => {
  const [session, setSession] = createSignal<AuthSession | null>(null);
  const [loading, setLoading] = createSignal(true);

  const clearLocalSession = () => {
    localStorage.removeItem(localAuthTokenKey);
    setSession(null);
  };

  createEffect(() => {
    void (async () => {
      const demoToken = localStorage.getItem(localAuthTokenKey);
      if (demoToken?.startsWith("demo.")) {
        const demoUser: LocalUser = {
          id: "demo-user-id",
          email: "demo@bookra.io",
          name: "Demo User",
        };
        setSession(sessionFromLocalToken(demoToken, demoUser));
        setLoading(false);
        return;
      }

      if (!authClient) {
        setSession(null);
        setLoading(false);
        return;
      }

      try {
        const response = await authClient.getSession();
        setSession((response?.data as unknown as AuthSession | undefined) ?? null);
      } catch {
        setSession(null);
      } finally {
        setLoading(false);
      }
    })();
  });

  const requireNeonAuth = () => {
    if (!authClient) {
      throw new Error("Neon Auth is not configured for this environment.");
    }
    return authClient;
  };

  const value: AuthContextValue = {
    session,
    loading,
    usesNeonAuth: () => Boolean(authClient),
    supportsMagicLink: () => false,
    supportsGoogleSignIn: () => Boolean(authClient),
    async getToken() {
      const demoToken = localStorage.getItem(localAuthTokenKey);
      if (demoToken?.startsWith("demo.")) {
        return demoToken;
      }
      if (!authClient) {
        return session()?.session?.token ?? null;
      }
      const jwtToken = await (
        authClient as unknown as { getJWTToken?: () => Promise<string | null | undefined> }
      ).getJWTToken?.();
      if (typeof jwtToken === "string" && jwtToken.trim() !== "") {
        return jwtToken;
      }
      return session()?.session?.token ?? null;
    },
    async signUpWithEmail(name: string, email: string, password: string) {
      const client = requireNeonAuth();
      await client.signUp.email({
        email,
        password,
        name,
      });
      const response = await client.getSession();
      setSession((response?.data as unknown as AuthSession | undefined) ?? null);
    },
    async signInWithEmail(email: string, password: string) {
      const client = requireNeonAuth();
      await client.signIn.email({
        email,
        password,
      });
      const response = await client.getSession();
      setSession((response?.data as unknown as AuthSession | undefined) ?? null);
    },
    async signInAsDemo() {
      const demoToken =
        "demo." +
        btoa(
          JSON.stringify({
            sub: "demo-user-id",
            email: "demo@bookra.io",
            name: "Demo User",
            exp: Math.floor(Date.now() / 1000) + 86400,
            iat: Math.floor(Date.now() / 1000),
            demo: true,
          }),
        ).replace(/=/g, "") +
        ".demo-signature";

      const demoUser: LocalUser = {
        id: "demo-user-id",
        email: "demo@bookra.io",
        name: "Demo User",
      };

      localStorage.setItem(localAuthTokenKey, demoToken);
      setSession(sessionFromLocalToken(demoToken, demoUser));
    },
    async sendMagicLink() {
      throw new Error("Magic link sign-in is not used in this app.");
    },
    async signInWithMagicLink() {
      throw new Error("Magic link callback is not used in this app.");
    },
    async signInWithGoogle() {
      const client = requireNeonAuth();
      await client.signIn.social({
        provider: "google",
        callbackURL: `${window.location.origin}/dashboard`,
      });
    },
    async signOut() {
      try {
        if (authClient) {
          await authClient.signOut();
        }
      } finally {
        clearLocalSession();
      }
    },
  };

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

export function useAuth() {
  const context = useContext(AuthContext);
  if (!context) {
    throw new Error("AuthProvider is missing from the component tree.");
  }
  return context;
}
