import { createHash, randomBytes } from "node:crypto";
import { createAdminClient } from "@/lib/supabase/admin";
import type { DeveloperApiToken } from "@/types";
import { allows, effectiveScopes, type ApiSection } from "@/lib/developers/scopes";

export interface AuthenticatedDeveloperRequest {
  admin: ReturnType<typeof createAdminClient>;
  token: DeveloperApiToken;
  workspaceUserId: string;
  workspaceTeamId: string | null;
}

export function generateDeveloperToken() {
  return `mb_${randomBytes(24).toString("base64url")}`;
}

export function hashDeveloperToken(token: string) {
  return createHash("sha256").update(token).digest("hex");
}

export function getTokenPrefix(token: string) {
  return token.slice(0, 12);
}

export function maskTokenPrefix(prefix: string) {
  if (prefix.length <= 8) return prefix;
  return `${prefix.slice(0, 4)}...${prefix.slice(-4)}`;
}

function extractBearerToken(request: Request) {
  const authorization = request.headers.get("authorization") || "";
  const [scheme, value] = authorization.split(" ");
  if (scheme?.toLowerCase() !== "bearer" || !value) {
    return null;
  }

  return value.trim();
}

export type FinanceAccessRequirement = "read" | "write";

/** Rights stored next to the token. Read separately: if a migration is missing, sections fall back safely. */
async function loadTokenRights(admin: ReturnType<typeof createAdminClient>, tokenId: string) {
  const full = await admin.from("developer_api_tokens").select("finance_access, scopes").eq("id", tokenId).maybeSingle();
  if (!full.error) return (full.data || {}) as { finance_access?: string | null; scopes?: unknown };
  const finance = await admin.from("developer_api_tokens").select("finance_access").eq("id", tokenId).maybeSingle();
  if (!finance.error) return (finance.data || {}) as { finance_access?: string | null };
  return {};
}

export async function authenticateDeveloperRequest(
  request: Request,
  options?: { write?: boolean; section?: ApiSection }
): Promise<AuthenticatedDeveloperRequest | null> {
  const tokenValue = extractBearerToken(request);
  if (!tokenValue) return null;

  const admin = createAdminClient();
  const tokenHash = hashDeveloperToken(tokenValue);
  const { data: token } = await admin
    .from("developer_api_tokens")
    .select(
      "id, user_id, team_id, name, access_level, token_prefix, last_used_at, expires_at, revoked_at, created_at, updated_at"
    )
    .eq("token_hash", tokenHash)
    .maybeSingle();

  const typedToken = (token || null) as DeveloperApiToken | null;
  if (!typedToken || typedToken.revoked_at) {
    return null;
  }

  if (typedToken.expires_at && new Date(typedToken.expires_at).getTime() < Date.now()) {
    return null;
  }

  if (options?.section) {
    // Per-section rights (Developers page). Tokens without stored scopes keep the old behaviour.
    const rights = await loadTokenRights(admin, typedToken.id);
    const scopes = effectiveScopes({ access_level: typedToken.access_level, ...rights });
    if (!allows(scopes, options.section, options.write ? "write" : "read")) return null;
  } else if (options?.write && typedToken.access_level !== "read_write") {
    return null;
  }

  void admin
    .from("developer_api_tokens")
    .update({ last_used_at: new Date().toISOString() })
    .eq("id", typedToken.id);

  return {
    admin,
    token: typedToken,
    workspaceUserId: typedToken.user_id,
    workspaceTeamId: typedToken.team_id,
  };
}

export async function canManageWorkspace(userId: string, teamId: string | null) {
  if (!teamId) return true;

  const admin = createAdminClient();
  const [{ data: ownedTeam }, { data: membership }] = await Promise.all([
    admin.from("teams").select("id").eq("id", teamId).eq("owner_id", userId).maybeSingle(),
    admin
      .from("team_members")
      .select("team_id, role")
      .eq("team_id", teamId)
      .eq("user_id", userId)
      .maybeSingle(),
  ]);

  if (ownedTeam) return true;
  return membership?.role === "admin" || membership?.role === "owner";
}

export async function canAccessWorkspace(userId: string, teamId: string | null) {
  if (!teamId) return true;

  const admin = createAdminClient();
  const [{ data: ownedTeam }, { data: membership }] = await Promise.all([
    admin.from("teams").select("id").eq("id", teamId).eq("owner_id", userId).maybeSingle(),
    admin
      .from("team_members")
      .select("team_id")
      .eq("team_id", teamId)
      .eq("user_id", userId)
      .maybeSingle(),
  ]);

  return Boolean(ownedTeam || membership);
}
