// deckControl.ts
// Deck request control layer.
// Deck owns route registration. deckControl owns external intake, record preflight, and reply relay.
// Recorder does not import this file.

import fs from "node:fs";
import { wakeRecorder, getRecorderStatus } from "./recorder";
import { capture } from "./capture";
import { controller } from "./controller";
import { validateManifestShape } from "./manifestContract";
import { SettingsPaths } from "./pathList";

export type RecordSource = "manual" | "tape" | "amp" | "aux" | "update";
export type DeckControlConfig = {
  ampAuth: boolean;
};

const config: DeckControlConfig = {
  ampAuth: true
};

const AMP_TOKEN_PROTECTED_PREFIXES = [
  "/record",
  "/manual/record",
  "/bridge/record",
  "/extension/record",
  "/import/record",
  "/remote/record",
  "/update/record",
  "/read/",
  "/logs/"
];

function isObject(v: unknown): v is Record<string, any> {
  return Boolean(v) && typeof v === "object" && !Array.isArray(v);
}

function readJson(filePath: string): any {
  try { return JSON.parse(fs.readFileSync(filePath, "utf8")); }
  catch { return {}; }
}

function sourceLabel(source: RecordSource): string {
  if (source === "amp") return "bridge";
  if (source === "aux") return "extension";
  if (source === "tape") return "monitor";
  if (source === "update") return "update";
  return "deck";
}

function normalizedSource(source: RecordSource): RecordSource {
  return ["manual", "tape", "amp", "aux", "update"].includes(source) ? source : "manual";
}

function isLocalRequest(req: any): boolean {
  const ip = String(req?.ip || "").replace(/^::ffff:/, "");
  const host = String(req?.headers?.host || "").toLowerCase();
  return ip === "127.0.0.1" || ip === "::1" || ip === "localhost" || host.startsWith("127.0.0.1:") || host.startsWith("localhost:");
}

function isAmpProtectedPath(pathname: string): boolean {
  const clean = String(pathname || "").split("?")[0];
  return AMP_TOKEN_PROTECTED_PREFIXES.some(prefix => clean === prefix.replace(/\/$/, "") || clean.startsWith(prefix));
}

function getAmpToken(req: any): { expected: string; received: string } {
  const user = readJson(SettingsPaths.user);
  const raw = req?.headers?.["x-amp-token"];
  return {
    expected: String(process.env.AMP_TOKEN || user?.bridgeAuth?.apiKey || "").trim(),
    received: Array.isArray(raw) ? String(raw[0] || "") : String(raw || "")
  };
}

export function configureDeckControl(next: Partial<DeckControlConfig>): DeckControlConfig {
  if (typeof next.ampAuth === "boolean") config.ampAuth = next.ampAuth;
  return getDeckControlConfig();
}

export function getDeckControlConfig(): DeckControlConfig {
  return { ...config };
}

export function bodyOf(req: any): Record<string, any> {
  return isObject(req?.body) ? req.body : {};
}

export function ampOut(reply: any, code: number, error: string, message?: string, extra: Record<string, any> = {}) {
  return reply.code(code).send({ ok: false, error, ...(message ? { message } : {}), ...extra });
}

export function relayRecorderResult(result: any, reply: any, extra: Record<string, any> = {}) {
  if (!result?.ok) return reply.code(400).send({ ...(result || {}), ...extra });
  return { ...result, ...extra };
}

export async function ampIn(req: any, reply: any) {
  if (!config.ampAuth) return;
  if (String(req?.method || "").toUpperCase() === "OPTIONS") return;

  const pathname = String(req?.url || "").split("?")[0];
  if (!isAmpProtectedPath(pathname) || isLocalRequest(req)) return;

  const token = getAmpToken(req);
  if (!token.expected || token.received !== token.expected) {
    capture.system({ tag: "DeckControl", event: "amp_auth_rejected", path: pathname, ip: req?.ip });
    return ampOut(reply, 401, "unauthorized");
  }
}

export async function recordIn(body: unknown, source: RecordSource, reply: any) {
  const cleanSource = normalizedSource(source);

  const contract = validateManifestShape(body, { requireTask: true, requirePayload: true });
  if (!contract.ok) {
    capture.system({ tag: "DeckControl", event: "manifest_contract_rejected", source: cleanSource, error: contract.error });
    return ampOut(reply, 400, contract.error || "invalid_manifest", contract.message || "Invalid recorder manifest.");
  }

  const authority = controller.canRecord();
  if (!authority.ok) {
    capture.system({ tag: "DeckControl", event: "record_authority_blocked", source: cleanSource, error: authority.error });
    return ampOut(reply, authority.error === "project_required_for_writes" ? 400 : 403, authority.error || "record_blocked", authority.message, {
      controllerState: authority.controllerState,
      usage: authority.usage,
      projectRails: authority.projectRails
    });
  }

  const result = await wakeRecorder(body, sourceLabel(cleanSource));
  if (!result.ok) return reply.code(400).send(result);

  const committed = controller.commitUsage();
  const usage = committed || authority.usage;
  const notice = committed?.message ? { code: committed.code, message: committed.message } : undefined;

  return relayRecorderResult(result, reply, {
    deckControl: { source: cleanSource },
    ...(authority.projectRails ? { projectRails: authority.projectRails } : {}),
    ...(usage ? { usage } : {}),
    ...(notice ? { notice } : {})
  });
}

export function recordRoute(source: RecordSource) {
  return async (req: any, reply: any) => recordIn(req.body, source, reply);
}

export async function recordCheck() {
  const authority = controller.canRecord();
  if (!authority.ok && authority.error === "project_required_for_writes") {
    return {
      ok: true,
      projectRequired: true,
      message: authority.message || "Select a project to continue.",
      controllerState: authority.controllerState,
      deckControl: getDeckControlConfig(),
      ...getRecorderStatus()
    };
  }

  return {
    ...(await wakeRecorder()),
    deckControl: getDeckControlConfig(),
    authority
  };
}
