import { eq, and } from "drizzle-orm";
import type {
  Adapter,
  AdapterUser,
  AdapterAccount,
  VerificationToken,
} from "next-auth/adapters";

import {
  user as userTable,
  accounts as accountsTable,
  sessions as sessionsTable,
  verificationTokens as verificationTokensTable,
} from "@/app/db/schema/users";
import { normaliseEmail } from "@/app/lib/email";

/**
 * Custom Drizzle adapter for NextAuth v4
 * ---------------------------------------
 * ✅ Works with bigint user IDs (no need for UUID migration)
 * ✅ Compatible with existing users table
 * ✅ Adds optional Database Session support
 *
 * By default, NextAuth uses JWT-based sessions (stateless).
 * The session functions here are only used if you enable:
 *
 *    session: { strategy: "database" }
 *
 * Benefits of database sessions:
 *  - Revocable sessions (logout from all devices)
 *  - View active sessions per user
 *  - Force re-login after role/permission changes
 *  - Audit trail of login activity
 */

// We extend

export default function DrizzleEmailAdapter(
  db: any,
  tables: {
    user: typeof userTable;
    accounts: typeof accountsTable;
    verificationTokens: typeof verificationTokensTable;
    sessions?: typeof sessionsTable;
  }
): Adapter {
  const { user, accounts, verificationTokens, sessions } = tables;

  //----------------------------------------------------------------------
  // Helpers
  //----------------------------------------------------------------------
  const toAdapterUser = (u: any): AdapterUser => ({
    id: String(u.id),
    dbId: String(u.id),
    email: u.email,
    name: u.firstName ?? null,
    image: u.image ?? null,
    emailVerified: u.emailVerified ?? null,
    onboarded: u.onboarded ?? false,
  });

  //----------------------------------------------------------------------
  // Adapter methods
  //----------------------------------------------------------------------
  return {
    //------------------------------------------------------------------
    // USERS
    //------------------------------------------------------------------
    async createUser(
      newUser: Omit<AdapterUser, "id"> & { id?: string }
    ): Promise<AdapterUser> {
      const [created] = await db
        .insert(user) // <-- now clearly the table
        .values({
          email: normaliseEmail(newUser.email!),
          firstName: newUser.name ?? null,
          image: newUser.image ?? null,
          emailVerified: newUser.emailVerified ?? null,
        })
        .returning();

      return toAdapterUser(created);
    },

    async getUser(id: string): Promise<AdapterUser | null> {
      const [found] = await db
        .select()
        .from(user)
        .where(eq(user.id, BigInt(id)));
      return found ? toAdapterUser(found) : null;
    },

    async getUserByEmail(email: string): Promise<AdapterUser | null> {
      const [found] = await db
        .select()
        .from(user)
        .where(eq(user.email, normaliseEmail(email)));
      return found ? toAdapterUser(found) : null;
    },

    async updateUser(
      u: Partial<AdapterUser> & Pick<AdapterUser, "id">
    ): Promise<AdapterUser> {
      const [updated] = await db
        .update(user)
        .set({
          firstName: u.name ?? null,
          image: u.image ?? null,
          emailVerified: u.emailVerified ?? null,
        })
        .where(eq(user.id, BigInt(u.id)))
        .returning();

      return toAdapterUser(updated);
    },
    async deleteUser(id: string): Promise<void> {
      await db.delete(user).where(eq(user.id, BigInt(id)));
    },

    //------------------------------------------------------------------
    // ACCOUNTS (OAuth)
    //------------------------------------------------------------------
    async linkAccount(account: AdapterAccount): Promise<void> {
      try {
        await db.insert(accounts).values({
          userId: BigInt(account.userId),
          provider: account.provider,
          providerAccountId: account.providerAccountId,
          type: account.type,
          refresh_token: account.refresh_token ?? null,
          access_token: account.access_token ?? null,
          expires_at: account.expires_at ?? null,
          token_type: account.token_type ?? null,
          scope: account.scope ?? null,
          id_token: account.id_token ?? null,
          session_state: account.session_state ?? null,
        });
      } catch (err: any) {
        console.error("Error linking account:", err);
        throw err;
      }
    },

    async unlinkAccount(params: {
      provider: string;
      providerAccountId: string;
    }): Promise<void> {
      await db
        .delete(accounts)
        .where(
          and(
            eq(accounts.provider, params.provider),
            eq(accounts.providerAccountId, params.providerAccountId)
          )
        );
    },

    async getUserByAccount(params: {
      provider: string;
      providerAccountId: string;
    }): Promise<AdapterUser | null> {
      const [acc] = await db
        .select()
        .from(accounts)
        .where(
          and(
            eq(accounts.provider, params.provider),
            eq(accounts.providerAccountId, params.providerAccountId)
          )
        );

      if (!acc) return null;

      const [usr] = await db
        .select()
        .from(user)
        .where(eq(user.id, BigInt(acc.userId)));
      return usr ? toAdapterUser(usr) : null;
    },

    //------------------------------------------------------------------
    // EMAIL VERIFICATION TOKENS
    //------------------------------------------------------------------
    async createVerificationToken(
      token: VerificationToken
    ): Promise<VerificationToken> {
      console.log("Creating verification token for:", token.identifier);
      const [created] = await db
        .insert(verificationTokens)
        .values({
          ...token,
          expires: new Date(token.expires), // keep as Date
        })
        .returning();
      return created as VerificationToken;
    },

    async useVerificationToken(params: {
      identifier: string;
      token: string;
    }): Promise<VerificationToken | null> {
      const [found] = await db
        .select()
        .from(verificationTokens)
        .where(
          and(
            eq(verificationTokens.identifier, params.identifier),
            eq(verificationTokens.token, params.token)
          )
        );

      if (!found) return null;

      await db
        .delete(verificationTokens)
        .where(
          and(
            eq(verificationTokens.identifier, params.identifier),
            eq(verificationTokens.token, params.token)
          )
        );

      return found as VerificationToken;
    },

    //------------------------------------------------------------------
    // SESSIONS (Optional – only used if session.strategy = "database")
    //------------------------------------------------------------------
    async createSession(session) {
      if (!sessions) return null;
      const [created] = await db
        .insert(sessions)
        .values({
          sessionToken: session.sessionToken,
          userId: BigInt(session.userId),
          expires: session.expires,
        })
        .returning();
      return created;
    },

    async getSessionAndUser(sessionToken) {
      if (!sessions) return null;

      const [session] = await db
        .select()
        .from(sessions)
        .where(eq(sessions.sessionToken, sessionToken));

      if (!session) return null;

      const [u] = await db
        .select()
        .from(user)
        .where(eq(user.id, BigInt(session.userId)));

      if (!u) return null;

      return {
        session,
        user: toAdapterUser(u),
      };
    },

    async updateSession(session) {
      if (!sessions) return null;
      const [updated] = await db
        .update(sessions)
        .set({ expires: session.expires })
        .where(eq(sessions.sessionToken, session.sessionToken))
        .returning();
      return updated ?? null;
    },

    async deleteSession(sessionToken) {
      if (!sessions) return;
      await db.delete(sessions).where(eq(sessions.sessionToken, sessionToken));
    },
  };
}
