import { createHash, randomBytes, timingSafeEqual } from "node:crypto";
import type { ResultSetHeader, RowDataPacket } from "mysql2/promise";

import {
  getAuthRequestMetadata,
  recordUserAuthActivity,
} from "@/lib/auth-activity";
import { ensureOfficialMailSchema, getDbPool } from "@/lib/db";
import { getSessionSnapshotByEmail } from "@/lib/official-mail";

const APP_TOKEN_PREFIX = "om_app_";
const APP_SESSION_DURATION_MS = 1000 * 60 * 60 * 24 * 30;

type AppSessionRow = RowDataPacket & {
  email: string;
  id: number;
  token_hash: string;
};

function hashToken(value: string) {
  return createHash("sha256").update(value, "utf8").digest("hex");
}

function hashesMatch(value: string, expectedHash: string) {
  const actual = Buffer.from(hashToken(value), "hex");
  const expected = Buffer.from(expectedHash, "hex");
  return actual.length === expected.length && timingSafeEqual(actual, expected);
}

function parseAppToken(value: string | null | undefined) {
  const normalized = String(value ?? "").trim();

  if (!normalized.startsWith(APP_TOKEN_PREFIX)) {
    return null;
  }

  const [selector, validator, ...rest] = normalized.slice(APP_TOKEN_PREFIX.length).split(".");

  if (
    rest.length > 0 ||
    !/^[a-f0-9]{32}$/.test(selector ?? "") ||
    !/^[A-Za-z0-9_-]{43}$/.test(validator ?? "")
  ) {
    return null;
  }

  return { selector, validator };
}

export function readAppBearerToken(requestHeaders: Headers) {
  const authorization = requestHeaders.get("authorization")?.trim() ?? "";
  const match = authorization.match(/^Bearer\s+(.+)$/i);
  return match?.[1]?.trim() ?? null;
}

export async function createAppSession(email: string, durationMs = APP_SESSION_DURATION_MS) {
  if (!Number.isSafeInteger(durationMs) || durationMs < 1 || durationMs > APP_SESSION_DURATION_MS) {
    throw new Error("app-session-duration-invalid");
  }
  await ensureOfficialMailSchema();
  const normalizedEmail = email.trim().toLowerCase();
  const [userRows] = await getDbPool().query<(RowDataPacket & { id: number })[]>(
    "SELECT id FROM users WHERE email = ? AND account_deleted_at IS NULL LIMIT 1",
    [normalizedEmail],
  );
  const userId = Number(userRows[0]?.id ?? 0);

  if (!userId) {
    throw new Error("app-session-user-not-found");
  }

  const selector = randomBytes(16).toString("hex");
  const validator = randomBytes(32).toString("base64url");
  const expiresAt = new Date(Date.now() + durationMs);

  await getDbPool().query(
    `
      DELETE FROM mailbox_app_sessions
      WHERE expires_at <= NOW()
         OR (revoked_at IS NOT NULL AND revoked_at < DATE_SUB(NOW(), INTERVAL 30 DAY))
    `,
  );
  await getDbPool().query(
    `
      INSERT INTO mailbox_app_sessions (user_id, selector, token_hash, expires_at, last_used_at)
      VALUES (?, ?, ?, ?, NOW())
    `,
    [userId, selector, hashToken(validator), expiresAt],
  );

  return {
    expiresAt: expiresAt.toISOString(),
    token: `${APP_TOKEN_PREFIX}${selector}.${validator}`,
  };
}

export async function getAppSessionUserByToken(
  token: string | null | undefined,
  requestHeaders?: Pick<Headers, "get">,
) {
  const parsed = parseAppToken(token);

  if (!parsed) {
    return null;
  }

  await ensureOfficialMailSchema();
  const [rows] = await getDbPool().query<AppSessionRow[]>(
    `
      SELECT app_session.id, app_session.token_hash, user.email
      FROM mailbox_app_sessions app_session
      INNER JOIN users user ON user.id = app_session.user_id
      WHERE app_session.selector = ?
        AND user.account_deleted_at IS NULL
        AND app_session.revoked_at IS NULL
        AND app_session.expires_at > NOW()
      LIMIT 1
    `,
    [parsed.selector],
  );
  const session = rows[0];

  if (!session || !hashesMatch(parsed.validator, session.token_hash)) {
    return null;
  }

  const [dailyUseResult] = await getDbPool().query<ResultSetHeader>(
    `
      UPDATE mailbox_app_sessions
      SET last_used_at = NOW(), updated_at = NOW()
      WHERE id = ?
        AND (last_used_at IS NULL OR last_used_at < CURDATE())
    `,
    [session.id],
  );

  if (dailyUseResult.affectedRows === 0) {
    await getDbPool().query(
      `
        UPDATE mailbox_app_sessions
        SET last_used_at = NOW(), updated_at = NOW()
        WHERE id = ?
          AND (last_used_at IS NULL OR last_used_at < DATE_SUB(NOW(), INTERVAL 10 MINUTE))
      `,
      [session.id],
    );
  } else if (requestHeaders) {
    await recordUserAuthActivity({
      ...getAuthRequestMetadata(requestHeaders, "app"),
      eventType: "login",
      identityEmail: session.email,
      lookup: "login_email",
      outcome: "success",
    });
  }

  return getSessionSnapshotByEmail(session.email);
}

export async function revokeAppSession(token: string | null | undefined) {
  const parsed = parseAppToken(token);

  if (!parsed) {
    return;
  }

  await ensureOfficialMailSchema();
  await getDbPool().query(
    `
      UPDATE mailbox_app_sessions
      SET revoked_at = COALESCE(revoked_at, NOW()), updated_at = NOW()
      WHERE selector = ?
    `,
    [parsed.selector],
  );
}
