import "server-only";
import { headers } from "next/headers";
import { cache } from "react";
import { redirect } from "next/navigation";
import { api } from "../../convex/_generated/api";
import { q, token } from "./convex-server";

/**
 * Session resolution on top of Convex Auth. The Convex backend is the only
 * source of truth for identity and permissions; this module just exposes the
 * same helpers pages and layouts always used.
 */
export interface SessionUser {
  id: string;
  code: string | null;
  email: string;
  fullName: string;
  avatarUrl: string | null;
  roleKey: string;
  roleName: string;
  locale: string;
  timezone: string;
  permissions: Set<string>;
  sessionId: string | null;
  isAdmin: boolean;
  twoFactorEnabled: boolean;
  lastLoginAt: Date | null;
  passwordChangedAt: Date | null;
}

export class AuthError extends Error {
  constructor(message = "Not authenticated") {
    super(message);
    this.name = "AuthError";
  }
}
export class PermissionError extends Error {
  constructor(public permission: string) {
    super(`Missing permission: ${permission}`);
    this.name = "PermissionError";
  }
}

// ── Client info ───────────────────────────────────────────────
export function parseUserAgent(ua: string | null) {
  const s = ua ?? "";
  let browser = "Unknown";
  if (/Edg\//.test(s)) browser = "Edge";
  else if (/OPR\//.test(s)) browser = "Opera";
  else if (/Chrome\//.test(s)) browser = "Chrome";
  else if (/Safari\//.test(s) && /Version\//.test(s)) browser = "Safari";
  else if (/Firefox\//.test(s)) browser = "Firefox";
  let device = "Desktop";
  if (/iPad|Tablet/i.test(s)) device = "Tablet";
  else if (/Mobile|iPhone|Android/i.test(s)) device = "Mobile";
  const os = /Mac OS X/.test(s) ? "macOS" : /Windows/.test(s) ? "Windows" : /Android/.test(s) ? "Android" : /iPhone|iPad/.test(s) ? "iOS" : /Linux/.test(s) ? "Linux" : "";
  return { browser, device: os ? `${device} · ${os}` : device, os };
}

export async function clientInfo() {
  const h = await headers();
  const ua = h.get("user-agent");
  const ip = h.get("x-forwarded-for")?.split(",")[0]?.trim() ?? h.get("x-real-ip") ?? "local";
  return { ua, ip, ...parseUserAgent(ua) };
}

/** Resolve the current user from the Convex Auth token. Cached per request. */
export const getCurrentUser = cache(async (): Promise<SessionUser | null> => {
  const t = await token();
  if (!t) return null;
  let me: Awaited<ReturnType<typeof fetchMe>> = null;
  try {
    me = await fetchMe();
  } catch {
    return null;
  }
  if (!me) return null;
  return {
    id: me.id,
    code: me.code,
    email: me.email,
    fullName: me.fullName,
    avatarUrl: null,
    roleKey: me.roleKey,
    roleName: me.roleName,
    locale: me.locale,
    timezone: me.timezone,
    permissions: new Set<string>(me.permissions),
    sessionId: me.sessionId,
    isAdmin: me.isAdmin,
    twoFactorEnabled: me.twoFactorEnabled,
    lastLoginAt: me.lastLoginAt ? new Date(me.lastLoginAt) : null,
    passwordChangedAt: me.passwordChangedAt ? new Date(me.passwordChangedAt) : null,
  };
});

async function fetchMe() {
  return (await q(api.security.me, {})) as null | { id: string; code: string | null; fullName: string; email: string; roleKey: string; roleName: string; isAdmin: boolean; permissions: string[]; locale: string; timezone: string; twoFactorEnabled: boolean; lastLoginAt: number | null; passwordChangedAt: number | null; sessionId: string | null };
}

export async function requireUser(): Promise<SessionUser> {
  const user = await getCurrentUser();
  if (!user) redirect("/login");
  return user;
}

export function can(user: SessionUser | null, permission: string): boolean {
  if (!user) return false;
  if (user.isAdmin) return true;
  return user.permissions.has(permission);
}

export function canAny(user: SessionUser | null, ...perms: string[]): boolean {
  return perms.some((p) => can(user, p));
}

/** For pages: redirects to /403 when the permission is missing. */
export async function requirePermission(...perms: string[]): Promise<SessionUser> {
  const user = await requireUser();
  if (!canAny(user, ...perms)) redirect(`/403?need=${encodeURIComponent(perms[0])}`);
  return user;
}

/** For server actions and route handlers: throws instead of redirecting. */
export async function assertPermission(...perms: string[]): Promise<SessionUser> {
  const user = await getCurrentUser();
  if (!user) throw new AuthError();
  if (!canAny(user, ...perms)) throw new PermissionError(perms[0]);
  return user;
}

export function isOnline(lastSeenAt: Date | number | string | null | undefined, windowMs = 3 * 60_000) {
  if (!lastSeenAt) return false;
  const t = lastSeenAt instanceof Date ? lastSeenAt.getTime() : typeof lastSeenAt === "number" ? lastSeenAt : Date.parse(lastSeenAt);
  return Date.now() - t < windowMs;
}
