// hypauthcall.ts
// Scope: the only Verbatim client file that knows Nodarama server endpoints and payload shapes.
// Everything server-shaped is normalized into AuthorityPack before leaving this file.

import fs from "node:fs";
import path from "node:path";
import crypto from "node:crypto";
import {
  getUser,
  getAuthority,
  getRemoteAccessToken,
  getRemoteRefreshToken,
  isAuthenticated,
  getUserTier,
  establishSession,
  clearSession,
  makeAuthorityPack,
  setAuthority,
  setRunLimitState,
  type UserTier,
  type UserRecord,
  type AuthorityPack
} from "./auth";
import { logSystem } from "../capture";

const ROOT = process.cwd();
const API_BASE = String(process.env.NODARAMA_API_BASE || "https://nodarama.com/api").replace(/\/$/, "");
const TIMEOUT_MS = 8000;
const POLICY_VERSION = "run-authority-v1";

export type BootAuthResult = {
  ok: boolean;
  tier: UserTier;
  serverUp: boolean;
  message: string;
  error?: string;
};

export type LoginResult = {
  ok: boolean;
  user?: UserRecord;
  error?: string;
  message?: string;
  failureType?: string;
  status?: number;
  connected?: boolean;
};

export type RegisterResult = LoginResult & {
  activationRequired?: boolean;
  activationId?: string;
  activationUrl?: string;
  tier?: string;
};

export type ActivationStatusResult = {
  ok: boolean;
  active?: boolean;
  user?: UserRecord;
  tier?: UserTier | string;
  message?: string;
  error?: string;
};

export type RunAuthorizationResult = {
  ok: boolean;
  allowed: boolean;
  source: string;
  reason: string;
  message: string;
  authority: AuthorityPack;
  connected: boolean;
  status: number;
};

export type RouteInstruction = {
  ok: boolean;
  route: string;
  strategy?: string;
  threshold?: number;
  systemPrompt?: string;
  mode?: string;
  lane?: string;
  via?: string;
  offline?: boolean;
};

export type HypTrigger = "error" | "chat" | "batch_complete";

const TRIGGER_CODE: Record<HypTrigger, string> = {
  error: "E",
  chat: "C",
  batch_complete: "B"
};

const ACTION_CLASS: Record<string, string> = {
  insertbefore: "I",
  insertafter: "I",
  insertbeforeit: "I",
  insertafterit: "I",
  replacestring: "II",
  replaceall: "II",
  replaceblock: "II",
  replacefunction: "IV",
  newfile: "III",
  rewritefile: "III",
  shell: "V",
  composite: "V",
  unknown: "II"
};

function apiUrl(endpoint: string): string {
  return `${API_BASE}/index.php?path=${encodeURIComponent(endpoint.replace(/^\//, ""))}`;
}

function clientVersion(): string {
  try {
    return String(JSON.parse(fs.readFileSync(path.join(ROOT, "package.json"), "utf8"))?.version ?? "0.0.0");
  } catch {
    return "0.0.0";
  }
}

function hypId(): string {
  return crypto.randomBytes(2).toString("hex").toUpperCase();
}

function hypToken(trigger: HypTrigger, actionType: string, itemCount: number): string {
  const t = TRIGGER_CODE[trigger] ?? "C";
  const a = ACTION_CLASS[String(actionType || "").toLowerCase()] ?? ACTION_CLASS.unknown;
  const d = Math.min(9, Math.max(1, Number(itemCount || 1)));
  return `${t}${a}${d}${hypId()}`;
}

async function post(endpoint: string, body: Record<string, any> = {}, token = ""): Promise<any> {
  const ctrl = new AbortController();
  const timeout = setTimeout(() => ctrl.abort(), TIMEOUT_MS);

  try {
    const headers: Record<string, string> = {
      "Content-Type": "application/json",
      "X-NV-Client": "verbatim"
    };
    if (token) headers.Authorization = `Bearer ${token}`;

    const res = await fetch(apiUrl(endpoint), {
      method: "POST",
      headers,
      body: JSON.stringify(body),
      signal: ctrl.signal
    });

    const text = await res.text().catch(() => "");
    let data: Record<string, any> = {};
    let parsedJson = false;

    if (text.trim()) {
      try {
        data = JSON.parse(text);
        parsedJson = true;
      } catch {
        data = { rawBody: text.slice(0, 500) };
      }
    }

    const remoteError = String(data.error || data.message || "").trim();
    const failureType = res.ok
      ? parsedJson ? "none" : "parse_failed"
      : res.status === 400 ? "bad_request"
      : res.status === 401 || res.status === 403 ? "auth_rejected"
      : res.status === 404 ? "remote_endpoint_not_found"
      : res.status === 409 ? "already_exists"
      : res.status >= 500 ? "remote_server_error"
      : "remote_request_failed";

    return {
      connected: true,
      status: res.status,
      endpoint,
      parsedJson,
      failureType,
      ok: res.ok && data.ok !== false,
      ...data,
      error: res.ok ? data.error : remoteError || data.error || failureType,
      remoteError
    };
  } catch (err: any) {
    const timedOut = err?.name === "AbortError";
    return {
      ok: false,
      connected: false,
      status: 0,
      endpoint,
      parsedJson: false,
      failureType: timedOut ? "timeout" : "network_error",
      error: timedOut ? "timeout" : err?.message ?? "network_error",
      remoteError: timedOut ? "timeout" : err?.message ?? "network_error"
    };
  } finally {
    clearTimeout(timeout);
  }
}

function authorityFrom(res: any, fallback = getAuthority()): AuthorityPack {
  return makeAuthorityPack({
    tier: res?.tier ?? res?.accessTier ?? res?.access,
    access: res?.access ?? res?.accessTier,
    licensed: res?.licensed,
    licenseStatus: res?.licenseStatus,
    packageName: res?.packageName ?? res?.package,
    price: res?.price,
    limitRemaining: res?.limitRemaining ?? res?.limit_remaining,
    policyVersion: res?.policyVersion ?? POLICY_VERSION,
    features: res?.features,
    authenticated: res?.authenticated,
    manifestVersion: res?.manifestVersion,
    updateChannel: res?.updateChannel,
    expiresAt: res?.expiresAt,
    serverTime: res?.serverTime,
    message: res?.message,
    reason: res?.reason,
    checkedAt: Date.now()
  }, fallback);
}

function identityFrom(res: any, fallback: Partial<UserRecord> = {}): { userId: string; email: string } {
  return {
    userId: String(res?.user?.id ?? res?.userId ?? fallback.userId ?? ""),
    email: String(res?.user?.email ?? res?.email ?? fallback.email ?? "")
  };
}

function establishFrom(res: any, fallback: Partial<UserRecord> = {}): UserRecord {
  const id = identityFrom(res, fallback);
  return establishSession({
    userId: id.userId,
    email: id.email,
    authority: authorityFrom(res, fallback.authority),
    remoteAccessToken: String(res?.accessToken ?? fallback.remoteAccessToken ?? ""),
    remoteRefreshToken: String(res?.refreshToken ?? fallback.remoteRefreshToken ?? ""),
    licenseKey: fallback.licenseKey ?? "",
    telemetry: fallback.telemetry
  });
}

export async function bootAuth(): Promise<BootAuthResult> {
  const refreshToken = getRemoteRefreshToken();

  if (!refreshToken) {
    logSystem("Hypauthcall", { event: "boot_auth", result: "unregistered", tier: "limited" });
    return { ok: true, tier: "limited", serverUp: false, message: "unregistered_limited_access" };
  }

  const cached = getUser();
  const res = await post("/auth/refresh", { refreshToken, clientVer: clientVersion() });

  if (!res.connected) {
    if (isAuthenticated()) {
      logSystem("Hypauthcall", { event: "boot_auth", result: "offline_cache", tier: cached.authority.tier });
      return { ok: true, tier: cached.authority.tier, serverUp: false, message: "server_unreachable_using_cache" };
    }
    return { ok: true, tier: "limited", serverUp: false, message: "server_unreachable_limited_access" };
  }

  if (res.ok && res.user) {
    const user = establishFrom(res, { ...cached, remoteRefreshToken: refreshToken });
    logSystem("Hypauthcall", { event: "boot_auth", result: "refreshed", tier: user.authority.tier });
    return { ok: true, tier: user.authority.tier, serverUp: true, message: "refreshed" };
  }

  if (res.status === 401 || res.status === 403 || res.error === "invalid_refresh_token" || res.error === "expired_refresh_token") {
    clearSession();
    return { ok: false, tier: "limited", serverUp: true, message: String(res.error || "auth_rejected") };
  }

  logSystem("Hypauthcall", { event: "boot_auth", result: "server_error", status: res.status });
  return { ok: true, tier: cached.authority.tier, serverUp: true, message: "server_error_using_cache" };
}

export async function login(email: string, password: string): Promise<LoginResult> {
  const res = await post("/auth/login", { email, password, clientVer: clientVersion() });

  if (!res.ok || !res.user) {
    const error = String(res.remoteError || res.error || "login_failed");
    logSystem("Hypauthcall", { event: "login_failed", status: res.status, failureType: res.failureType, remoteError: error });
    return {
      ok: false,
      error,
      message: String(res.message || error),
      failureType: String(res.failureType || "login_failed"),
      status: Number(res.status || 0),
      connected: Boolean(res.connected)
    };
  }

  const user = establishFrom(res, { email });
  logSystem("Hypauthcall", { event: "login", userId: user.userId, tier: user.authority.tier });
  return { ok: true, user };
}

export async function register(email: string, password: string, options?: {
  deviceName?: string;
  deviceFingerprint?: string;
}): Promise<RegisterResult> {
  const res = await post("/auth/register", {
    email,
    password,
    deviceName: options?.deviceName || "NV Station",
    deviceFingerprint: options?.deviceFingerprint || "",
    clientVer: clientVersion()
  });

  if (res.ok && res.user) {
    const user = establishFrom(res, { email });
    logSystem("Hypauthcall", { event: "register", userId: user.userId, tier: user.authority.tier });
    return { ok: true, user };
  }

  if (res.activationRequired) {
    return {
      ok: true,
      activationRequired: true,
      activationId: String(res.activationId ?? ""),
      activationUrl: String(res.activationUrl ?? ""),
      message: String(res.message ?? "Activation pending"),
      tier: String(res.tier ?? res.access ?? "limited")
    };
  }

  const error = String(res.remoteError || res.error || res.message || "register_failed");
  return {
    ok: false,
    error,
    message: String(res.message || error),
    failureType: String(res.failureType || "register_failed"),
    status: Number(res.status || 0),
    connected: Boolean(res.connected)
  };
}

export async function checkActivation(activationId: string, options?: {
  deviceName?: string;
  deviceFingerprint?: string;
}): Promise<ActivationStatusResult> {
  const res = await post("/auth/activation-status", {
    activationId,
    deviceName: options?.deviceName || "NV Station",
    deviceFingerprint: options?.deviceFingerprint || "",
    clientVer: clientVersion()
  });

  if (res.ok && res.active && res.user) {
    const user = establishFrom(res);
    return { ok: true, active: true, user, tier: user.authority.tier };
  }

  if (res.connected) {
    return {
      ok: true,
      active: false,
      message: String(res.message ?? "Activation pending"),
      tier: String(res.tier ?? res.access ?? "limited")
    };
  }

  return { ok: false, error: String(res.error || "network_error") };
}

export async function authorizeRun(params: {
  source: string;
  action?: string;
  task?: string;
  clientPolicyVersion?: string;
}): Promise<RunAuthorizationResult> {
  const token = getRemoteAccessToken();
  const source = String(params.source || "").trim() || "unknown";

  if (!token) {
    const authority = setAuthority({
      limitRemaining: 0,
      policyVersion: POLICY_VERSION,
      message: "Run authorization requires an active session.",
      reason: "missing_access_token",
      checkedAt: Date.now()
    });

    return {
      ok: false,
      allowed: false,
      source,
      reason: authority.reason,
      message: authority.message,
      authority,
      connected: false,
      status: 0
    };
  }

  const res = await post("/auth/authorize-run", {
    source,
    action: String(params.action || ""),
    clientVer: clientVersion(),
    clientPolicyVersion: String(params.clientPolicyVersion || POLICY_VERSION)
  }, token);

  const authority = setAuthority(authorityFrom(res));
  const allowed = Boolean(res.ok && res.allowed);
  const result = {
    ok: Boolean(res.ok),
    allowed,
    source,
    reason: String(res.reason || res.error || authority.reason || (allowed ? "authorized" : "run_authorization_failed")),
    message: String(res.message || authority.message || (allowed ? "Run authorized." : "Run authorization failed.")),
    authority,
    connected: Boolean(res.connected),
    status: Number(res.status || (allowed ? 200 : 0))
  };

  setRunLimitState({
    limitRemaining: authority.limitRemaining,
    policyVersion: authority.policyVersion,
    message: result.message,
    reason: result.reason
  });

  logSystem("Hypauthcall", {
    event: "authorize_run_result",
    source,
    allowed,
    reason: result.reason,
    limitRemaining: authority.limitRemaining,
    status: result.status
  });

  return result;
}

export async function requestRoute(params: {
  trigger: HypTrigger;
  actionType: string;
  itemCount: number;
  via?: string;
}): Promise<RouteInstruction> {
  const token = getRemoteAccessToken();
  const authority = getAuthority();
  const hyp = hypToken(params.trigger, params.actionType, params.itemCount);

  const res = await post("/route", {
    via: params.via || "verbatim",
    hyp,
    access: authority.access || authority.tier,
    clientVer: clientVersion(),
    contextShape: {
      trigger: params.trigger,
      batchMode: params.trigger === "batch_complete",
      itemCount: Math.max(1, Number(params.itemCount || 1))
    }
  }, token);

  if (!res.ok) return { ok: true, route: "local_default", offline: !res.connected };

  return {
    ok: true,
    route: String(res.route || res.strategy || res.lane || "local_default"),
    strategy: res.strategy ? String(res.strategy) : undefined,
    threshold: res.threshold !== undefined ? Number(res.threshold) : undefined,
    systemPrompt: res.systemPrompt ? String(res.systemPrompt) : undefined,
    mode: res.mode ? String(res.mode) : undefined,
    lane: res.lane ? String(res.lane) : undefined,
    via: res.via ? String(res.via) : undefined,
    offline: false
  };
}

export async function uploadTelemetry(tokens: string[]): Promise<void> {
  const user = getUser();
  const token = getRemoteAccessToken();
  if (!token || !tokens.length || !user.telemetry.structural) return;

  try {
    await post("/telemetry", {
      tokens,
      tier: user.authority.tier,
      ts: Date.now()
    }, token);
  } catch {}
}

export async function requestAuthorityPacket(): Promise<any> {
  const token = getRemoteAccessToken();

  if (!token) {
    return { ok: false, connected: false, status: 0, error: "missing_access_token", message: "No remote access token available." };
  }

  const res = await post("/system/authority", { clientVer: clientVersion() }, token);

  if (res.ok) {
    const authority = setAuthority(authorityFrom(res));
    return { ...res, authority, tier: authority.tier, accessTier: authority.tier };
  }

  return res;
}

export async function requestSystemManifest(): Promise<any> {
  const token = getRemoteAccessToken();
  if (!token) return { ok: false, connected: false, status: 0, error: "missing_access_token" };
  return await post("/system/manifest", { clientVer: clientVersion() }, token);
}

export async function reportSystemIntegrity(report: any): Promise<any> {
  const token = getRemoteAccessToken();
  if (!token) return { ok: false, connected: false, status: 0, error: "missing_access_token" };
  return await post("/system/tamper-report", { ...report, clientVer: clientVersion() }, token);
}

export async function requestUpdateManifest(channel = "stable"): Promise<any> {
  return await post("/update/manifest", {
    clientVer: clientVersion(),
    channel
  }, getRemoteAccessToken());
}

export async function keepalive(): Promise<void> {
  const token = getRemoteAccessToken();
  if (!token) return;

  try {
    await post("/auth/keepalive", {}, token);
  } catch {}
}