import { prisma } from "./db";
import { auth } from "@/auth";
import { requirePermission as checkPermission, type Action } from "./permissions";
export { can, PermissionError } from "./permissions";
export type { Action } from "./permissions";

export type AppUser = {
  id: string;
  authentikUid: string | null;
  username: string | null;
  displayName: string | null;
  email: string;
  avatarUrl: string;
  role: "admin" | "reviewer" | "viewer" | "editor" | "owner";
  orgId?: string;
};

type CacheEntry = { user: AppUser; ts: number };
const userCache = new Map<string, CacheEntry>();
const CACHE_TTL_MS = 60_000;

function mapGroupsToRole(groups: string[]): AppUser["role"] {
  const lower = groups.map((g) => g.toLowerCase());
  if (lower.some((g) => g.includes("admin"))) return "admin";
  if (lower.some((g) => g.includes("reviewer") || g.includes("review"))) return "reviewer";
  return "viewer";
}

/**
 * Get current user from Auth.js session (primary) or Authentik headers (legacy fallback).
 */
export async function getSessionUser(): Promise<AppUser | null> {
  const session = await auth();
  if (session?.user) {
    const cached = userCache.get(session.user.id);
    if (cached && Date.now() - cached.ts < CACHE_TTL_MS) return cached.user;

    const dbUser = await prisma.user.findUnique({
      where: { id: session.user.id },
      include: { memberships: { take: 1 } },
    });

    if (dbUser) {
      const user: AppUser = {
        id: dbUser.id,
        authentikUid: dbUser.authentikUid,
        username: dbUser.username,
        displayName: dbUser.displayName,
        email: dbUser.email,
        avatarUrl: dbUser.avatarUrl,
        role: (dbUser.memberships[0]?.role || dbUser.role) as AppUser["role"],
        orgId: dbUser.memberships[0]?.organizationId,
      };
      userCache.set(user.id, { user, ts: Date.now() });
      return user;
    }
  }

  return null;
}

/**
 * Legacy: get user from Authentik forward-auth headers.
 * Kept for backward compatibility during migration.
 */
export async function getOrCreateUser(headers: Headers): Promise<AppUser | null> {
  const sessionUser = await getSessionUser();
  if (sessionUser) return sessionUser;

  const uid = headers.get("x-authentik-uid") ?? "";
  const username = headers.get("x-authentik-username") ?? "";
  const email = headers.get("x-authentik-email") ?? "";

  if (!uid && !username && !email) return null;

  const cacheKey = uid || username || email;
  const cached = userCache.get(cacheKey);
  if (cached && Date.now() - cached.ts < CACHE_TTL_MS) {
    return cached.user;
  }

  const name = headers.get("x-authentik-name") ?? "";
  const groupsRaw = headers.get("x-authentik-groups") ?? "";
  const groups = groupsRaw ? groupsRaw.split("|") : [];
  const role = mapGroupsToRole(groups);

  const dbUser = await prisma.user.upsert({
    where: { authentikUid: uid || `fallback-${username || email}` },
    update: {
      username: username || undefined,
      displayName: name || username || undefined,
      email: email || undefined,
      role,
    },
    create: {
      authentikUid: uid || `fallback-${username || email}`,
      username: username || email,
      displayName: name || username || email,
      email,
      role,
    },
  });

  const user: AppUser = {
    id: dbUser.id,
    authentikUid: dbUser.authentikUid,
    username: dbUser.username,
    displayName: dbUser.displayName,
    email: dbUser.email,
    avatarUrl: dbUser.avatarUrl,
    role: dbUser.role as AppUser["role"],
  };

  userCache.set(cacheKey, { user, ts: Date.now() });
  return user;
}

export function requireRole(user: AppUser | null, ...roles: AppUser["role"][]): void {
  if (!user || !roles.includes(user.role)) {
    throw new RoleError("Insufficient permissions");
  }
}

export class RoleError extends Error {
  constructor(message: string) {
    super(message);
    this.name = "RoleError";
  }
}

/**
 * Get the authenticated user and verify they have a specific permission.
 * Returns the user or throws PermissionError / returns null if unauthenticated.
 */
export async function requireAuth(action?: Action): Promise<AppUser> {
  const user = await getSessionUser();
  if (!user) {
    throw new RoleError("Authentication required");
  }
  if (action) {
    checkPermission(user.role, action);
  }
  return user;
}

/**
 * Returns the org scope for Prisma queries based on the current user's membership.
 */
export function withOrgScope(user: AppUser): { organizationId: string } | {} {
  return user.orgId ? { organizationId: user.orgId } : {};
}
