import "server-only";
import { cookies } from "next/headers";
import { RoleCode, UserStatus } from "@prisma/client";

import { prisma } from "@/lib/prisma";

export type UserRole = "super_admin" | "admin_client" | "agent";

export type AuthSession = {
  userId: string;
  fullName: string;
  email: string;
  role: UserRole;
  tenantId: string;
  tenantName: string;
  issuedAt: number;
  expiresAt: number;
  extraPermissions: string[];
};

export const SESSION_COOKIE = "wa_inbox_session";
const SESSION_TTL_SECONDS = 60 * 60 * 24 * 7;
const AUTH_SECRET = process.env.AUTH_SECRET;
const SESSION_ITERATIONS = 120000;

const DEFAULT_SECRET = "whatsapp-inbox-dev-secret";
const resolvedAuthSecret =
  AUTH_SECRET || (process.env.NODE_ENV === "production" ? undefined : DEFAULT_SECRET);

if (!resolvedAuthSecret) {
  throw new Error("AUTH_SECRET is required in production for secure session signing.");
}

const textEncoder = new TextEncoder();
const textDecoder = new TextDecoder();

const SESSION_SECRET_FALLBACK = textEncoder.encode(resolvedAuthSecret);

type RolePermissionSet = Set<string>;

function normalizePermissionToken(value: unknown) {
  if (typeof value !== "string") {
    return null;
  }

  const trimmed = value.trim();
  return trimmed.length > 0 ? trimmed : null;
}

function collectPermissionGrants(target: RolePermissionSet, rawValue: unknown) {
  const source =
    typeof rawValue === "string" && rawValue.length > 0
      ? rawValue.trim()
      : rawValue;

  if (typeof source === "string") {
    try {
      const parsed = JSON.parse(source);
      collectPermissionGrants(target, parsed);
      return;
    } catch {
      return;
    }
  }

  if (!source) {
    return;
  }

  if (Array.isArray(source)) {
    for (const item of source) {
      const token = normalizePermissionToken(item);
      if (!token) {
        continue;
      }
      target.add(token);
    }
    return;
  }

  if (typeof source !== "object" || source === null) {
    return;
  }

  const entries = Object.entries(source as Record<string, unknown>);
  for (const [key, entry] of entries) {
    if (key === "global" && entry === true) {
      target.add("*");
      continue;
    }

    if (key === "tenant" && entry === true) {
      target.add("tenant:read");
      continue;
    }

    if (key === "inbox" && entry === true) {
      target.add("inbox:read");
      target.add("inbox:assign");
      target.add("inbox:status");
      target.add("inbox:reply");
      target.add("inbox:notes");
      target.add("inbox:tags");
      continue;
    }

    if (key === "admin" && entry === true) {
      target.add("admin:read");
      target.add("admin:manage");
      continue;
    }

    if (key === "agent" && entry === true) {
      target.add("agent:read");
      target.add("agent:manage");
      continue;
    }

    if (key === "profile_manage_self" && entry === true) {
      target.add("profile:manage_self");
      continue;
    }

    if (typeof entry === "string") {
      const token = normalizePermissionToken(entry);
      if (!token) {
        continue;
      }
      target.add(token);
      continue;
    }

    if (entry === true) {
      const token = normalizePermissionToken(key);
      if (token) {
        target.add(token);
      }
      continue;
    }

    collectPermissionGrants(target, entry);
  }
}

function parseRolePermissions(rawPermissions: string | null | undefined) {
  const permissionSet: RolePermissionSet = new Set<string>();
  collectPermissionGrants(permissionSet, rawPermissions);
  return Array.from(permissionSet);
}

function uint8ToBase64Url(bytes: Uint8Array) {
  let str = "";
  for (const byte of bytes) {
    str += String.fromCharCode(byte);
  }

  return btoa(str).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/g, "");
}

function base64UrlToUint8(value: string) {
  const normalized = value.replace(/-/g, "+").replace(/_/g, "/");
  const padded = normalized + "=".repeat((4 - (normalized.length % 4)) % 4);
  const binary = atob(padded);
  const bytes = new Uint8Array(binary.length);
  for (let i = 0; i < binary.length; i += 1) {
    bytes[i] = binary.charCodeAt(i);
  }
  return bytes;
}

async function getSessionHmacKey() {
  return crypto.subtle.importKey(
    "raw",
    SESSION_SECRET_FALLBACK,
    { name: "HMAC", hash: "SHA-256" },
    false,
    ["sign", "verify"]
  );
}

function equalBytes(a: Uint8Array, b: Uint8Array) {
  if (a.length !== b.length) {
    return false;
  }

  let diff = 0;
  for (let i = 0; i < a.length; i += 1) {
    diff |= a[i] ^ b[i];
  }
  return diff === 0;
}

function resolveRoleFromCode(code: RoleCode): UserRole {
  switch (code) {
    case RoleCode.SUPER_ADMIN:
      return "super_admin";
    case RoleCode.AGENT:
      return "agent";
    case RoleCode.ADMIN_CLIENT:
    default:
      return "admin_client";
  }
}

export async function serializeSession(session: AuthSession) {
  const payload = `${session.userId}|${session.role}|${session.tenantId}|${session.expiresAt}`;
  const key = await getSessionHmacKey();
  const signature = await crypto.subtle.sign("HMAC", key, textEncoder.encode(payload));

  return `${uint8ToBase64Url(textEncoder.encode(payload))}.${uint8ToBase64Url(new Uint8Array(signature))}`;
}

export async function parseSessionCookie(raw: string) {
  try {
    const [payload, signature] = raw.split(".");
    if (!payload || !signature) {
      return null;
    }

    const key = await getSessionHmacKey();
    const payloadBytes = base64UrlToUint8(payload);
    const signatureBytes = base64UrlToUint8(signature);
    const valid = await crypto.subtle.verify("HMAC", key, signatureBytes, payloadBytes);
    if (!valid) {
      return null;
    }

    const decoded = textDecoder.decode(payloadBytes);
    const [userId, role, tenantId, exp] = decoded.split("|");
    if (!userId || !role || !tenantId || !exp) {
      return null;
    }

    const expiresAt = Number(exp);
    if (!Number.isFinite(expiresAt) || expiresAt < Math.floor(Date.now() / 1000)) {
      return null;
    }

    return {
      userId,
      role: role as UserRole,
      tenantId,
      tenantName: "",
      fullName: "",
      email: "",
      issuedAt: Math.floor(Date.now() / 1000),
      expiresAt
    };
  } catch {
    return null;
  }
}

export async function getSession() {
  const store = await cookies();
  const raw = store.get(SESSION_COOKIE)?.value;
  if (!raw) {
    return null;
  }

  const parsed = await parseSessionCookie(raw);
  if (!parsed) {
    return null;
  }

  const user = await prisma.user.findUnique({
    where: { id: parsed.userId },
    include: { role: true, tenant: true }
  });

  if (!user) {
    return null;
  }

  return {
    userId: user.id,
    fullName: user.fullName,
    email: user.email,
    role: resolveRoleFromCode(user.role.code),
    tenantId: user.tenantId,
    tenantName: user.tenant.companyName || user.tenant.name,
    extraPermissions: parseRolePermissions(user.role.permissionsJson),
    issuedAt: parsed.issuedAt,
    expiresAt: parsed.expiresAt
  };
}

function normalizeSalt(value: string) {
  if (!value) {
    return null;
  }

  try {
    return base64UrlToUint8(value);
  } catch {
    return null;
  }
}

export async function hashPassword(password: string) {
  const salt = new Uint8Array(16);
  crypto.getRandomValues(salt);

  const passwordBits = textEncoder.encode(password);
  const key = await crypto.subtle.importKey("raw", passwordBits, "PBKDF2", false, ["deriveBits"]);
  const digest = await crypto.subtle.deriveBits(
    {
      name: "PBKDF2",
      hash: "SHA-256",
      salt,
      iterations: SESSION_ITERATIONS
    },
    key,
    32 * 8
  );

  return `pbkdf2$${SESSION_ITERATIONS}$${uint8ToBase64Url(salt)}$${uint8ToBase64Url(new Uint8Array(digest))}`;
}

export async function verifyPassword(password: string, hash: string) {
  if (!password || !hash) {
    return false;
  }

  const parts = hash.split("$");
  if (parts.length !== 4 || parts[0] !== "pbkdf2") {
    return false;
  }

  const [, iterationRaw, salt, expectedDigest] = parts;
  if (!iterationRaw || !salt || !expectedDigest) {
    return false;
  }

  const iterations = Number(iterationRaw);
  if (!Number.isFinite(iterations) || iterations <= 0) {
    return false;
  }

  const normalizedSalt = normalizeSalt(salt);
  const normalizedHash = normalizeSalt(expectedDigest);
  if (!normalizedSalt || !normalizedHash) {
    return false;
  }

  const passwordBits = textEncoder.encode(password);
  const key = await crypto.subtle.importKey("raw", passwordBits, "PBKDF2", false, ["deriveBits"]);
  const derived = await crypto.subtle.deriveBits(
    {
      name: "PBKDF2",
      hash: "SHA-256",
      salt: normalizedSalt,
      iterations
    },
    key,
    normalizedHash.byteLength * 8
  );

  return equalBytes(new Uint8Array(derived), normalizedHash);
}

export async function authenticateUser(email: string, password: string) {
  if (!email || !password) {
    return null;
  }

  const user = await prisma.user.findUnique({
    where: { email },
    include: { tenant: true, role: true }
  });

  if (!user || user.status !== UserStatus.ACTIVE) {
    return null;
  }

  if (!(await verifyPassword(password, user.passwordHash))) {
    return null;
  }

  const now = Math.floor(Date.now() / 1000);
  return {
    userId: user.id,
    fullName: user.fullName,
    email: user.email,
    role: resolveRoleFromCode(user.role.code),
    tenantId: user.tenantId,
    tenantName: user.tenant.companyName || user.tenant.name,
    extraPermissions: parseRolePermissions(user.role.permissionsJson),
    issuedAt: now,
    expiresAt: now + SESSION_TTL_SECONDS
  };
}

export function getDefaultPathForRole(role: UserRole) {
  if (role === "super_admin") {
    return "/super-admin";
  }

  if (role === "agent") {
    return "/agent";
  }

  return "/dashboard";
}

export function canAccessPath(role: UserRole, pathname: string) {
  if (pathname.startsWith("/super-admin")) {
    return role === "super_admin";
  }

  if (pathname.startsWith("/agent")) {
    return role === "agent";
  }

  const adminPaths = [
    "/dashboard",
    "/inbox",
    "/contacts",
    "/templates",
    "/campaigns",
    "/team",
    "/reports",
    "/settings",
    "/billing",
    "/audit-log"
  ];

  if (adminPaths.some((path) => pathname.startsWith(path))) {
    return role === "admin_client";
  }

  if (pathname.startsWith("/profile") || pathname.startsWith("/notifications") || pathname.startsWith("/search")) {
    return true;
  }

  return true;
}
