import { readFileSync } from "node:fs";
import { homedir } from "node:os";
import { join } from "node:path";

const DEFAULT_BASE_URL = "http://127.0.0.1:8787";
const DEFAULT_TIMEOUT_MS = 500;
const CACHE_TTL_MS = 5_000;

export type AdmissionVerdict = {
  eligible: boolean;
  reason: string;
};

export type SpineFetcher = (
  input: string | URL | Request,
  init?: RequestInit,
) => Promise<Response>;

export type AdmissionClientOptions = {
  baseUrl?: string;
  tokenPath?: string;
  timeoutMs?: number;
  fetcher?: SpineFetcher;
  now?: () => number;
};

type CacheEntry = {
  expiresAt: number;
  verdict: AdmissionVerdict;
};

const eligibilityCache = new Map<string, CacheEntry>();

function configDir(): string {
  return process.env.OVERDECK_CONFIG_DIR ?? join(homedir(), ".config", "overdeck");
}

function resolveTokenPath(opts?: AdmissionClientOptions): string {
  return opts?.tokenPath ?? join(configDir(), "token");
}

function readToken(opts?: AdmissionClientOptions): string | null {
  try {
    const contents = readFileSync(resolveTokenPath(opts), "utf8").trim();
    return contents.length > 0 ? contents : null;
  } catch {
    return null;
  }
}

function cacheKey(host: string, command: string): string {
  return `${host}\0${command}`;
}

export function clearAdmissionCache(): void {
  eligibilityCache.clear();
}

export async function hostEligible(
  host: string,
  command: string,
  opts?: AdmissionClientOptions,
): Promise<AdmissionVerdict> {
  if (!process.env.OVERDECK_SPINE_OBEY) {
    return { eligible: true, reason: "controller-absent" };
  }

  const now = opts?.now ?? Date.now;
  const key = cacheKey(host, command);
  const cached = eligibilityCache.get(key);
  if (cached && cached.expiresAt > now()) {
    return cached.verdict;
  }

  const token = readToken(opts);
  if (!token) {
    return { eligible: true, reason: "controller-absent" };
  }

  const baseUrl = opts?.baseUrl ?? DEFAULT_BASE_URL;
  const timeoutMs = opts?.timeoutMs ?? DEFAULT_TIMEOUT_MS;
  const fetcher = opts?.fetcher ?? fetch;

  try {
    const url = new URL("/admission/eligible", baseUrl);
    url.searchParams.set("host", host);
    url.searchParams.set("command", command);

    const response = await fetcher(url, {
      method: "GET",
      headers: {
        authorization: `Bearer ${token}`,
      },
      signal: AbortSignal.timeout(timeoutMs),
    });

    if (!response.ok) {
      return { eligible: true, reason: "controller-absent" };
    }

    const body = (await response.json()) as Partial<AdmissionVerdict>;
    if (typeof body.eligible !== "boolean" || typeof body.reason !== "string") {
      return { eligible: true, reason: "controller-absent" };
    }

    const verdict = { eligible: body.eligible, reason: body.reason };
    eligibilityCache.set(key, { expiresAt: now() + CACHE_TTL_MS, verdict });
    return verdict;
  } catch {
    return { eligible: true, reason: "controller-absent" };
  }
}
