import { compare } from 'bcryptjs';
import { eq } from 'drizzle-orm';
import NextAuth from 'next-auth';
import Credentials from 'next-auth/providers/credentials';
import { z } from 'zod';
import { memberships, organizations, users } from '@/db/schema';
import {
  getDefaultBusinessRole,
  getDefaultPermissions,
  isBusinessRole,
  isSystemRole
} from '@/lib/auth/rbac';
import { db } from '@/lib/db';

const signInSchema = z.object({
  email: z.string().email(),
  password: z.string().min(8)
});

const authSecret =
  process.env.AUTH_SECRET ??
  process.env.NEXTAUTH_SECRET ??
  (process.env.NODE_ENV !== 'production' ? 'dev-only-auth-secret-change-me' : undefined);

type SessionOrganization = {
  id: string;
  name: string;
  slug: string;
  role: string;
  businessRole: string;
  plan: string;
  imageUrl: string | null;
};

async function buildUserAccessContext(userId: string) {
  const dbUser = await db.query.users.findFirst({
    where: eq(users.id, userId)
  });

  if (!dbUser) {
    return null;
  }

  const membershipRows = await db
    .select({
      organizationId: memberships.organizationId,
      role: memberships.role,
      businessRole: memberships.businessRole,
      permissions: memberships.permissions,
      name: organizations.name,
      slug: organizations.slug,
      plan: organizations.plan,
      imageUrl: organizations.imageUrl
    })
    .from(memberships)
    .innerJoin(organizations, eq(organizations.id, memberships.organizationId))
    .where(eq(memberships.userId, dbUser.id));

  const systemRole = isSystemRole(dbUser.systemRole) ? dbUser.systemRole : 'user';
  const rows =
    systemRole === 'super_admin'
      ? (await db.select().from(organizations)).map((organization) => {
          const membership = membershipRows.find(
            (row) => row.organizationId === organization.id
          );

          return {
            organizationId: organization.id,
            role: membership?.role ?? 'admin',
            businessRole:
              membership?.businessRole && isBusinessRole(membership.businessRole)
                ? membership.businessRole
                : getDefaultBusinessRole('admin'),
            permissions:
              membership?.permissions ??
              getDefaultPermissions(
                membership?.role === 'admin' ? 'admin' : 'user',
                membership?.businessRole && isBusinessRole(membership.businessRole)
                  ? membership.businessRole
                  : getDefaultBusinessRole('admin')
              ),
            name: organization.name,
            slug: organization.slug,
            plan: organization.plan,
            imageUrl: organization.imageUrl
          };
        })
      : membershipRows;

  const activeMembership =
    rows.find((row) => row.organizationId === dbUser.activeOrganizationId) ?? rows[0] ?? null;

  if (!dbUser.activeOrganizationId && activeMembership) {
    await db
      .update(users)
      .set({ activeOrganizationId: activeMembership.organizationId })
      .where(eq(users.id, dbUser.id));
  }

  const sessionOrganizations: SessionOrganization[] = rows.map((row) => ({
    id: row.organizationId,
    name: row.name,
    slug: row.slug,
    role: row.role,
    businessRole: row.businessRole,
    plan: row.plan,
    imageUrl: row.imageUrl
  }));

  return {
    dbUser,
    sessionOrganizations,
    activeMembership
  };
}

export const { handlers, auth, signIn, signOut } = NextAuth({
  trustHost: true,
  secret: authSecret,
  pages: {
    signIn: '/auth/sign-in'
  },
  session: {
    strategy: 'jwt'
  },
  providers: [
    Credentials({
      credentials: {
        email: { label: 'Email', type: 'email' },
        password: { label: 'Password', type: 'password' }
      },
      authorize: async (credentials) => {
        const parsed = signInSchema.safeParse(credentials);

        if (!parsed.success) {
          return null;
        }

        const user = await db.query.users.findFirst({
          where: eq(users.email, parsed.data.email.toLowerCase())
        });

        if (!user) {
          return null;
        }

        const isValid = await compare(parsed.data.password, user.passwordHash);

        if (!isValid) {
          return null;
        }

        return {
          id: user.id,
          name: user.name,
          email: user.email,
          image: user.image,
          systemRole: isSystemRole(user.systemRole) ? user.systemRole : 'user',
          activeOrganizationId: user.activeOrganizationId
        };
      }
    })
  ],
  callbacks: {
    jwt: async ({ token, user }) => {
      if (user) {
        token.sub = user.id;
      }

      if (!token.sub) {
        return token;
      }

      const accessContext = await buildUserAccessContext(token.sub);

      if (!accessContext) {
        return token;
      }

      const { dbUser, sessionOrganizations, activeMembership } = accessContext;
      const systemRole = isSystemRole(dbUser.systemRole) ? dbUser.systemRole : 'user';

      token.name = dbUser.name;
      token.email = dbUser.email;
      token.picture = dbUser.image ?? null;
      token.systemRole = systemRole;
      token.activeOrganizationId = activeMembership?.organizationId ?? null;
      token.activeOrganizationName = activeMembership?.name ?? null;
      token.activeOrganizationPlan = activeMembership?.plan ?? null;
      token.activeMembershipRole = activeMembership?.role ?? null;
      token.activeBusinessRole = activeMembership?.businessRole ?? null;
      token.activePermissions = activeMembership?.permissions ?? [];
      token.organizations = sessionOrganizations;

      return token;
    },
    session: async ({ session, token }) => {
      if (!token.sub || !session.user) {
        return session;
      }

      session.user.id = token.sub;
      session.user.name = token.name ?? session.user.name ?? null;
      session.user.email = token.email ?? session.user.email ?? null;
      session.user.image = typeof token.picture === 'string' ? token.picture : null;
      session.user.systemRole = token.systemRole === 'super_admin' ? 'super_admin' : 'user';
      session.user.activeOrganizationId =
        typeof token.activeOrganizationId === 'string' ? token.activeOrganizationId : null;
      session.user.activeOrganizationName =
        typeof token.activeOrganizationName === 'string' ? token.activeOrganizationName : null;
      session.user.activeOrganizationPlan =
        typeof token.activeOrganizationPlan === 'string' ? token.activeOrganizationPlan : null;
      session.user.activeMembershipRole =
        typeof token.activeMembershipRole === 'string' ? token.activeMembershipRole : null;
      session.user.activeBusinessRole =
        typeof token.activeBusinessRole === 'string' ? token.activeBusinessRole : null;
      session.user.activePermissions = Array.isArray(token.activePermissions)
        ? token.activePermissions.filter((value): value is string => typeof value === 'string')
        : [];
      session.user.organizations = Array.isArray(token.organizations)
        ? token.organizations.filter(
            (value): value is SessionOrganization =>
              typeof value === 'object' &&
              value !== null &&
              typeof value.id === 'string' &&
              typeof value.name === 'string' &&
              typeof value.slug === 'string' &&
              typeof value.role === 'string' &&
              typeof value.businessRole === 'string' &&
              typeof value.plan === 'string'
          )
        : [];

      return session;
    }
  }
});
