/**
 * Xác thực và phiên đăng nhập.
 *
 * Mật khẩu băm bằng scrypt của Node (không cần thư viện ngoài). Phiên đăng nhập
 * là JWT ký HS256, lưu trong cookie httpOnly — không cần bảng session, hệ thống
 * scale ngang được mà không cần bộ nhớ chung.
 */
import "server-only";
import { cookies } from "next/headers";
import { SignJWT, jwtVerify } from "jose";
import { eq, and } from "drizzle-orm";
import { db } from "@/db";
import { users, tenants } from "@/db/schema";
import { hashPassword, verifyPassword } from "./password";

export { hashPassword, verifyPassword };

const COOKIE_NAME = "crm_session";
const SESSION_DAYS = 7;

export type SessionUser = {
  userId: string;
  tenantId: string;
  email: string;
  fullName: string;
  role: "DIRECTOR" | "GDKV" | "MANAGER" | "SALES";
  territoryId: string | null;
  tenantName: string;
  /** Thu hẹp báo cáo vào một khu vực / phòng ban(team) / nhân viên — chỉ đặt bởi `withFocus`, không nằm trong cookie phiên. */
  focus?: ReportFocus;
};

export type ReportFocus = { type: "user" | "dept" | "terr"; id: string };

/* -------------------------------------------------------------------------- */
/* Phiên đăng nhập                                                            */
/* -------------------------------------------------------------------------- */

function secretKey(): Uint8Array {
  const secret = process.env.SESSION_SECRET;
  if (!secret || secret.length < 32) {
    throw new Error("SESSION_SECRET phải có tối thiểu 32 ký tự. Xem hướng dẫn trong .env.example.");
  }
  return new TextEncoder().encode(secret);
}

export async function createSession(user: SessionUser): Promise<void> {
  const token = await new SignJWT({ ...user })
    .setProtectedHeader({ alg: "HS256" })
    .setIssuedAt()
    .setExpirationTime(`${SESSION_DAYS}d`)
    .sign(secretKey());

  const store = await cookies();
  store.set(COOKIE_NAME, token, {
    httpOnly: true,
    secure: process.env.NODE_ENV === "production",
    sameSite: "lax",
    path: "/",
    maxAge: SESSION_DAYS * 24 * 60 * 60,
  });
}

export async function destroySession(): Promise<void> {
  const store = await cookies();
  store.delete(COOKIE_NAME);
}

/**
 * Trả về người dùng đang đăng nhập, hoặc null nếu chưa/không hợp lệ.
 *
 * Chữ ký JWT hợp lệ chưa đủ: token còn hạn 7 ngày, nên tài khoản bị khoá hoặc
 * xoá vẫn dùng được tới khi hết hạn nếu chỉ tin vào chữ ký. Vì vậy mỗi request
 * đều đối chiếu lại với bảng users — một truy vấn theo khoá chính, không đáng kể
 * so với rủi ro để người đã bị thu hồi quyền tiếp tục truy cập.
 */
export async function getSession(): Promise<SessionUser | null> {
  const store = await cookies();
  const token = store.get(COOKIE_NAME)?.value;
  if (!token) return null;

  let claims: SessionUser;
  let issuedAt = 0;
  try {
    const { payload } = await jwtVerify(token, secretKey());
    claims = payload as unknown as SessionUser;
    issuedAt = Number(payload.iat ?? 0);
  } catch {
    return null;
  }

  const [current] = await db
    .select({
      id: users.id,
      tenantId: users.tenantId,
      email: users.email,
      fullName: users.fullName,
      role: users.role,
      territoryId: users.territoryId,
      isActive: users.isActive,
      passwordChangedAt: users.passwordChangedAt,
      tenantName: tenants.name,
      tenantActive: tenants.isActive,
    })
    .from(users)
    .innerJoin(tenants, eq(users.tenantId, tenants.id))
    .where(eq(users.id, claims.userId))
    .limit(1);

  if (!current || !current.isActive || !current.tenantActive) return null;

  // Đổi/đặt lại mật khẩu thì mọi phiên đăng nhập cấp trước đó hết hiệu lực — người biết mật khẩu cũ
  // (hoặc đang giữ phiên bị lộ) không dùng tiếp được. Phiên mới cấp sau khi đổi có `iat` không nhỏ hơn mốc này.
  if (current.passwordChangedAt && issuedAt < Math.floor(current.passwordChangedAt.getTime() / 1000)) return null;

  // Lấy vai trò và tên từ cơ sở dữ liệu, không lấy từ token: quản trị viên đổi
  // quyền là có hiệu lực ngay, không phải chờ người dùng đăng nhập lại.
  return {
    userId: current.id,
    tenantId: current.tenantId,
    email: current.email,
    fullName: current.fullName,
    role: current.role,
    territoryId: current.territoryId,
    tenantName: current.tenantName,
  };
}

/**
 * Dùng trong mọi trang và server action cần đăng nhập. Ném lỗi thay vì trả null
 * để không thể vô tình truy vấn dữ liệu khi chưa có phiên hợp lệ.
 */
export async function requireSession(): Promise<SessionUser> {
  const session = await getSession();
  if (!session) throw new Error("UNAUTHENTICATED");
  return session;
}

/* -------------------------------------------------------------------------- */
/* Đăng nhập                                                                  */
/* -------------------------------------------------------------------------- */

/** Sai quá số lần này thì khoá tạm — chặn dò mật khẩu bằng cách thử liên tục. */
const MAX_FAILED_ATTEMPTS = 5;
const LOCKOUT_MINUTES = 15;

export async function authenticate(
  email: string,
  password: string,
): Promise<{ ok: true; user: SessionUser } | { ok: false; error: string }> {
  const rows = await db
    .select({
      id: users.id,
      tenantId: users.tenantId,
      email: users.email,
      fullName: users.fullName,
      role: users.role,
      territoryId: users.territoryId,
      passwordHash: users.passwordHash,
      isActive: users.isActive,
      failedLoginCount: users.failedLoginCount,
      lockedUntil: users.lockedUntil,
      tenantName: tenants.name,
    })
    .from(users)
    .innerJoin(tenants, eq(users.tenantId, tenants.id))
    .where(and(eq(users.email, email.trim().toLowerCase()), eq(tenants.isActive, true)))
    .limit(1);

  const row = rows[0];
  // Vẫn chạy một phép băm khi không tìm thấy email, để thời gian phản hồi
  // không tiết lộ email nào tồn tại trong hệ thống.
  if (!row) {
    await verifyPassword(password, "scrypt$00$00");
    return { ok: false, error: "Email hoặc mật khẩu không đúng." };
  }
  if (!row.isActive) {
    return { ok: false, error: "Tài khoản đã bị khoá. Liên hệ quản trị viên." };
  }

  if (row.lockedUntil && row.lockedUntil.getTime() > Date.now()) {
    // Vẫn băm để thời gian phản hồi giống các nhánh khác — không tiết lộ qua độ trễ.
    await verifyPassword(password, "scrypt$00$00");
    const minutesLeft = Math.ceil((row.lockedUntil.getTime() - Date.now()) / 60000);
    return {
      ok: false,
      error: `Tài khoản tạm khoá do nhập sai mật khẩu nhiều lần. Thử lại sau ${minutesLeft} phút.`,
    };
  }

  if (!(await verifyPassword(password, row.passwordHash))) {
    const failedCount = row.failedLoginCount + 1;
    const lockingNow = failedCount >= MAX_FAILED_ATTEMPTS;
    await db
      .update(users)
      .set({
        failedLoginCount: lockingNow ? 0 : failedCount,
        lockedUntil: lockingNow ? new Date(Date.now() + LOCKOUT_MINUTES * 60_000) : null,
      })
      .where(eq(users.id, row.id));

    if (lockingNow) {
      return {
        ok: false,
        error: `Nhập sai quá ${MAX_FAILED_ATTEMPTS} lần — tài khoản tạm khoá ${LOCKOUT_MINUTES} phút.`,
      };
    }
    return { ok: false, error: "Email hoặc mật khẩu không đúng." };
  }

  await db
    .update(users)
    .set({ lastLoginAt: new Date(), failedLoginCount: 0, lockedUntil: null })
    .where(eq(users.id, row.id));

  return {
    ok: true,
    user: {
      userId: row.id,
      tenantId: row.tenantId,
      email: row.email,
      fullName: row.fullName,
      role: row.role,
      territoryId: row.territoryId,
      tenantName: row.tenantName,
    },
  };
}
