import { getRuntimeEnv } from "./runtime-env";
import { ensureSchema, getRawDb } from "./mvp-db";

const env = getRuntimeEnv();

export type UserRole = "super_admin" | "admin" | "user";

export type AuthUser = {
  id: string;
  username: string;
  role: UserRole;
};

export type RequestPrincipal =
  | { kind: "internal" }
  | { kind: "user"; user: AuthUser };

const SESSION_COOKIE = "koc_session";
const SESSION_MAX_AGE_SECONDS = 60 * 60 * 24 * 7;
// Keep existing password records compatible while using a production-safe cost.
const PASSWORD_ITERATIONS = 100_000;

function bytesToHex(bytes: Uint8Array) {
  return [...bytes].map((byte) => byte.toString(16).padStart(2, "0")).join("");
}

function hexToBytes(value: string) {
  if (!/^(?:[0-9a-f]{2})+$/i.test(value)) return new Uint8Array();
  return new Uint8Array(value.match(/.{2}/g)?.map((part) => Number.parseInt(part, 16)) ?? []);
}

function utf8(value: string) {
  return new TextEncoder().encode(value);
}

async function sha256(value: string) {
  return bytesToHex(new Uint8Array(await crypto.subtle.digest("SHA-256", utf8(value))));
}

async function derivePasswordHash(
  password: string,
  saltHex: string,
  iterations: number,
) {
  const key = await crypto.subtle.importKey(
    "raw",
    utf8(password),
    "PBKDF2",
    false,
    ["deriveBits"],
  );
  const bits = await crypto.subtle.deriveBits(
    {
      name: "PBKDF2",
      hash: "SHA-256",
      salt: hexToBytes(saltHex),
      iterations,
    },
    key,
    256,
  );
  return bytesToHex(new Uint8Array(bits));
}

function safeEqual(left: string, right: string) {
  if (left.length !== right.length) return false;
  let mismatch = 0;
  for (let index = 0; index < left.length; index += 1) {
    mismatch |= left.charCodeAt(index) ^ right.charCodeAt(index);
  }
  return mismatch === 0;
}

export function normalizeUsername(value: unknown) {
  return String(value ?? "").normalize("NFKC").trim().toLowerCase();
}

export function validateUsername(value: string) {
  return /^[\p{L}\p{N}_.@+-]{2,32}$/u.test(value);
}

export function validatePassword(value: string) {
  return value.length >= 8 && value.length <= 72;
}

export async function createPasswordRecord(password: string) {
  const salt = new Uint8Array(16);
  crypto.getRandomValues(salt);
  const passwordSalt = bytesToHex(salt);
  return {
    passwordHash: await derivePasswordHash(
      password,
      passwordSalt,
      PASSWORD_ITERATIONS,
    ),
    passwordSalt,
    passwordIterations: PASSWORD_ITERATIONS,
  };
}

export async function verifyPassword(
  password: string,
  record: {
    password_hash: string;
    password_salt: string;
    password_iterations: number;
  },
) {
  const candidate = await derivePasswordHash(
    password,
    record.password_salt,
    record.password_iterations,
  );
  return safeEqual(candidate, record.password_hash);
}

function getAuthEnv() {
  return env as unknown as {
    SUPER_ADMIN_USERNAME?: string;
    SUPER_ADMIN_PASSWORD?: string;
    ADMIN_INTERNAL_TOKEN?: string;
  };
}

export async function ensureInitialSuperAdmin() {
  await ensureSchema();
  const db = getRawDb();
  const existing = await db
    .prepare("SELECT id FROM users WHERE role = 'super_admin' LIMIT 1")
    .first<{ id: string }>();
  if (existing) return;

  const username = normalizeUsername(getAuthEnv().SUPER_ADMIN_USERNAME);
  const password = String(getAuthEnv().SUPER_ADMIN_PASSWORD ?? "");
  if (!validateUsername(username) || !validatePassword(password)) {
    throw new Error("超级管理员账号尚未配置，请设置 SUPER_ADMIN_USERNAME 和 SUPER_ADMIN_PASSWORD");
  }
  const conflict = await db
    .prepare("SELECT id FROM users WHERE username = ?")
    .bind(username)
    .first<{ id: string }>();
  if (conflict) {
    throw new Error("超级管理员用户名已被占用，请更换 SUPER_ADMIN_USERNAME");
  }
  const passwordRecord = await createPasswordRecord(password);
  try {
    await db
      .prepare(
        `INSERT INTO users
          (id, username, password_hash, password_salt, password_iterations, role)
         VALUES (?, ?, ?, ?, ?, 'super_admin')`,
      )
      .bind(
        crypto.randomUUID(),
        username,
        passwordRecord.passwordHash,
        passwordRecord.passwordSalt,
        passwordRecord.passwordIterations,
      )
      .run();
  } catch {
    const concurrent = await db
      .prepare("SELECT id FROM users WHERE role = 'super_admin' LIMIT 1")
      .first<{ id: string }>();
    if (!concurrent) throw new Error("超级管理员初始化失败");
  }
}

export function sessionCookieFromHeader(cookieHeader: string | null) {
  const match = String(cookieHeader ?? "")
    .split(";")
    .map((part) => part.trim())
    .find((part) => part.startsWith(`${SESSION_COOKIE}=`));
  return match ? decodeURIComponent(match.slice(SESSION_COOKIE.length + 1)) : "";
}

export function createSessionCookie(token: string, secure = true) {
  return [
    `${SESSION_COOKIE}=${encodeURIComponent(token)}`,
    "Path=/",
    "HttpOnly",
    "SameSite=Lax",
    secure ? "Secure" : "",
    `Max-Age=${SESSION_MAX_AGE_SECONDS}`,
  ]
    .filter(Boolean)
    .join("; ");
}

export function clearSessionCookie(secure = true) {
  return [
    `${SESSION_COOKIE}=`,
    "Path=/",
    "HttpOnly",
    "SameSite=Lax",
    secure ? "Secure" : "",
    "Max-Age=0",
  ]
    .filter(Boolean)
    .join("; ");
}

export function requestUsesHttps(request: Request) {
  const forwardedProtocol = request.headers
    .get("x-forwarded-proto")
    ?.split(",")[0]
    ?.trim()
    .toLowerCase();
  return forwardedProtocol === "https" || new URL(request.url).protocol === "https:";
}

export async function createSession(userId: string) {
  const tokenBytes = new Uint8Array(32);
  crypto.getRandomValues(tokenBytes);
  const token = bytesToHex(tokenBytes);
  const tokenHash = await sha256(token);
  const expiresAt = new Date(Date.now() + SESSION_MAX_AGE_SECONDS * 1000).toISOString();
  const db = getRawDb();
  await db.batch([
    db
      .prepare("DELETE FROM auth_sessions WHERE expires_at <= ?")
      .bind(new Date().toISOString()),
    db
      .prepare(
        `INSERT INTO auth_sessions (token_hash, user_id, expires_at)
         VALUES (?, ?, ?)`,
      )
      .bind(tokenHash, userId, expiresAt),
  ]);
  return token;
}

export async function deleteSession(token: string) {
  if (!token) return;
  await ensureSchema();
  await getRawDb()
    .prepare("DELETE FROM auth_sessions WHERE token_hash = ?")
    .bind(await sha256(token))
    .run();
}

export async function getUserFromSessionToken(token: string): Promise<AuthUser | null> {
  if (!token) return null;
  await ensureSchema();
  const row = await getRawDb()
    .prepare(
      `SELECT u.id, u.username, u.role
       FROM auth_sessions s
       JOIN users u ON u.id = s.user_id
       WHERE s.token_hash = ? AND s.expires_at > ?
       LIMIT 1`,
    )
    .bind(await sha256(token), new Date().toISOString())
    .first<{ id: string; username: string; role: string }>();
  if (!row || !["super_admin", "admin", "user"].includes(row.role)) return null;
  return { id: row.id, username: row.username, role: row.role as UserRole };
}

export async function getUserFromCookieHeader(cookieHeader: string | null) {
  return getUserFromSessionToken(sessionCookieFromHeader(cookieHeader));
}

function hasInternalToken(request: Request) {
  const expected = String(getAuthEnv().ADMIN_INTERNAL_TOKEN ?? "").trim();
  const received = String(request.headers.get("x-koc-admin-token") ?? "").trim();
  return Boolean(expected && received && safeEqual(expected, received));
}

export async function getRequestPrincipal(
  request: Request,
): Promise<RequestPrincipal | null> {
  if (hasInternalToken(request)) return { kind: "internal" };
  const user = await getUserFromCookieHeader(request.headers.get("cookie"));
  return user ? { kind: "user", user } : null;
}

export async function isAppRequest(request: Request) {
  return Boolean(await getRequestPrincipal(request));
}

export async function isManagerRequest(request: Request) {
  const principal = await getRequestPrincipal(request);
  return Boolean(
    principal &&
      (principal.kind === "internal" ||
        principal.user.role === "super_admin" ||
        principal.user.role === "admin"),
  );
}

export function authForbidden() {
  return Response.json({ error: "请先登录后再操作" }, { status: 401 });
}

export function managerForbidden() {
  return Response.json({ error: "当前账号没有管理权限" }, { status: 403 });
}
