import NextAuth from "next-auth";
import Credentials from "next-auth/providers/credentials";
import { compare } from "bcryptjs";
import { eq } from "drizzle-orm";
import { getDb } from "@/core/db/client";
import { tenantUsers, users } from "@/core/db/schema";
import { emailSchema, passwordSchema } from "@/core/validation/schemas";
import type { PlatformRole, TenantRole } from "@/core/authorization/authorize";

declare module "next-auth" {
  interface Session {
    user: {
      id: string;
      name?: string | null;
      email?: string | null;
      platformRole: PlatformRole;
      tenantMemberships: Array<{
        tenantId: string;
        role: TenantRole;
        permissions: string[];
        status: string;
      }>;
    };
  }

  interface User {
    platformRole: PlatformRole;
    tenantMemberships: Session["user"]["tenantMemberships"];
  }
}

const MEMBERSHIP_REFRESH_MS = 5 * 60 * 1000;

/** Secure cookies only when the public app URL is HTTPS (not merely NODE_ENV=production). */
function shouldUseSecureAuthCookies(): boolean {
  const publicUrl = process.env.AUTH_URL ?? process.env.APP_URL ?? "";
  return publicUrl.startsWith("https://");
}

async function loadMemberships(userId: string) {
  const db = getDb();
  return db
    .select({
      tenantId: tenantUsers.tenantId,
      role: tenantUsers.role,
      permissions: tenantUsers.permissions,
      status: tenantUsers.status,
    })
    .from(tenantUsers)
    .where(eq(tenantUsers.userId, userId));
}

export const { handlers, auth, signIn, signOut, unstable_update } = NextAuth({
  session: {
    strategy: "jwt",
    maxAge: 60 * 60 * 12, // 12 hours
    updateAge: 60 * 30,
  },
  pages: {
    signIn: "/?auth=login",
  },
  cookies: {
    sessionToken: {
      name: shouldUseSecureAuthCookies()
        ? "__Secure-authjs.session-token"
        : "authjs.session-token",
      options: {
        httpOnly: true,
        sameSite: "lax",
        path: "/",
        secure: shouldUseSecureAuthCookies(),
      },
    },
  },
  providers: [
    Credentials({
      name: "credentials",
      credentials: {
        email: { label: "Email", type: "email" },
        password: { label: "Password", type: "password" },
      },
      async authorize(credentials) {
        const emailParsed = emailSchema.safeParse(credentials?.email);
        const passwordParsed = passwordSchema.safeParse(credentials?.password);
        if (!emailParsed.success || !passwordParsed.success) {
          return null;
        }

        const db = getDb();
        const email = emailParsed.data.toLowerCase();
        const [user] = await db
          .select()
          .from(users)
          .where(eq(users.email, email))
          .limit(1);

        if (!user || !user.passwordHash || user.status === "disabled") {
          return null;
        }

        const valid = await compare(passwordParsed.data, user.passwordHash);
        if (!valid) {
          return null;
        }

        const memberships = await loadMemberships(user.id);

        await db
          .update(users)
          .set({ lastLoginAt: new Date(), updatedAt: new Date() })
          .where(eq(users.id, user.id));

        return {
          id: user.id,
          name: user.name,
          email: user.email,
          platformRole: user.platformRole,
          tenantMemberships: memberships.map((m) => ({
            tenantId: m.tenantId,
            role: m.role as TenantRole,
            permissions: m.permissions ?? [],
            status: m.status,
          })),
        };
      },
    }),
  ],
  callbacks: {
    async jwt({ token, user, trigger, session }) {
      if (user) {
        token.sub = user.id;
        token.name = user.name;
        token.email = user.email;
        token.platformRole = user.platformRole;
        token.tenantMemberships = user.tenantMemberships;
        token.membershipsCheckedAt = Date.now();
        return token;
      }

      if (trigger === "update") {
        const updateUser = session?.user as
          | { name?: string | null; email?: string | null }
          | undefined;
        if (typeof updateUser?.name === "string") {
          token.name = updateUser.name;
        }
        if (typeof updateUser?.email === "string") {
          token.email = updateUser.email;
        }
        // Force a membership/identity refresh on the next gated check.
        token.membershipsCheckedAt = 0;
      }

      const checkedAt =
        typeof token.membershipsCheckedAt === "number"
          ? token.membershipsCheckedAt
          : 0;

      if (token.sub && Date.now() - checkedAt > MEMBERSHIP_REFRESH_MS) {
        const db = getDb();
        const [dbUser] = await db
          .select({
            platformRole: users.platformRole,
            status: users.status,
            name: users.name,
            email: users.email,
          })
          .from(users)
          .where(eq(users.id, token.sub))
          .limit(1);

        if (!dbUser || dbUser.status === "disabled") {
          token.sub = undefined;
          token.tenantMemberships = [];
          token.platformRole = "none";
          return token;
        }

        const memberships = await loadMemberships(token.sub);
        token.platformRole = dbUser.platformRole;
        token.name = dbUser.name;
        token.email = dbUser.email;
        token.tenantMemberships = memberships.map((m) => ({
          tenantId: m.tenantId,
          role: m.role as TenantRole,
          permissions: m.permissions ?? [],
          status: m.status,
        }));
        token.membershipsCheckedAt = Date.now();
      }

      return token;
    },
    async session({ session, token }) {
      if (session.user) {
        session.user.id = token.sub ?? "";
        if (typeof token.name === "string") {
          session.user.name = token.name;
        }
        if (typeof token.email === "string") {
          session.user.email = token.email;
        }
        session.user.platformRole =
          (token.platformRole as PlatformRole) ?? "none";
        session.user.tenantMemberships =
          (token.tenantMemberships as Array<{
            tenantId: string;
            role: TenantRole;
            permissions: string[];
            status: string;
          }>) ?? [];
      }
      return session;
    },
  },
  trustHost: true,
});
