// auth.ts
// Scope: user authentication, session management, license state, and access tier.
// Owns user.json — the single source of truth for whether the user is licensed and what access the app grants.
// Called by deck.ts for middleware, by power.ts for boot auth check, by hypauthcall.ts for session.

import fs from "node:fs";
import crypto from "node:crypto";
import {
  USER_PATH,
  AUTH_TOKEN_PATH as TOKEN_PATH,
  AUTH_ACCESS_PATH as REMOTE_ACCESS_PATH,
  AUTH_REFRESH_PATH as REMOTE_REFRESH_PATH
} from "../pathList";

// ── Types ─────────────────────────────────────────────────────────────────────

export type UserTier = "limited" | "unlimited" | "backstage";

export type TelemetryConsent = {
  structural: boolean;   // structural hypauth tokens, controlled by access tier
  logs:       boolean;   // anonymized log patterns, controlled by access tier
  updatedAt:  number;
};

export type UserRecord = {
  userId:               string;
  email:                string;
  tier:                 UserTier;
  licensed:             boolean;
  sessionToken:         string;
  tokenExpiresAt:       number;
  remoteAccessToken:    string;
  remoteRefreshToken:   string;
  telemetry:            TelemetryConsent;
  installedAt:          number;
  lastAuthAt:           number;
  licenseKey:           string;
};

export type AuthResult = {
  ok:      boolean;
  userId?: string;
  tier?:   UserTier;
  licensed?: boolean;
  error?:  string;
};

// ── Defaults ──────────────────────────────────────────────────────────────────

const GUEST_USER: UserRecord = {
  userId:             "",
  email:              "",
  tier:               "limited",
  licensed:           false,
  sessionToken:       "",
  tokenExpiresAt:     0,
  remoteAccessToken:  "",
  remoteRefreshToken: "",
  telemetry: {
    structural: true,
    logs:       true,
    updatedAt:  0
  },
  installedAt:  Date.now(),
  lastAuthAt:   0,
  licenseKey:   ""
};

// Session duration: 30 days
const SESSION_TTL_MS = 30 * 24 * 60 * 60 * 1000;

// ── Internal helpers ──────────────────────────────────────────────────────────

function generateToken(): string {
  return crypto.randomBytes(32).toString("hex");
}

function readUser(): UserRecord {
  try {
    const raw = JSON.parse(fs.readFileSync(USER_PATH, "utf8"));
    const tier = normalizeAccessTier(raw?.accessTier ?? raw?.tier);
    const licensed = Boolean(raw?.licensed ?? (tier !== "limited" && raw?.userId));
    return { ...GUEST_USER, ...raw, tier, licensed };
  } catch {
    return { ...GUEST_USER };
  }
}

function writeUser(user: UserRecord): void {
  fs.writeFileSync(USER_PATH, JSON.stringify(user, null, 2), "utf8");
}

function isTokenValid(user: UserRecord): boolean {
  if (!user.sessionToken || !user.tokenExpiresAt) return false;
  return Date.now() < user.tokenExpiresAt;
}

function normalizeAccessTier(value: unknown): UserTier {
  const clean = String(value || "").trim().toLowerCase();
  if (clean === "backstage") return "backstage";
  if (clean === "unlimited" || clean === "standard" || clean === "custom" || clean === "lifetime_custom") return "unlimited";
  return "limited";
}
export function getUser(): UserRecord {
  return readUser();
}

export function getUserTier(): UserTier {
  const user = readUser();
  if (!isTokenValid(user)) return "limited";
  return user.tier;
}

export function setRuntimeUserTier(tier: UserTier): void {
  const user = readUser();
  const accessTier = normalizeAccessTier(tier);
  writeUser({
    ...user,
    tier: accessTier,
    licensed: accessTier !== "limited",
    lastAuthAt: Date.now()
  });
}

export function isAuthenticated(): boolean {
  return isTokenValid(readUser());
}

export function isLicensed(): boolean {
  const user = readUser();
  return isTokenValid(user) && user.licensed && user.tier !== "limited";
}

export function getTelemetryConsent(): TelemetryConsent {
  return readUser().telemetry;
}

// Called by deck middleware — validates token from request header
export function validateRequestToken(token: string): AuthResult {
  if (!token) return { ok: false, error: "missing_token" };
  const user = readUser();

  // Allow unauthenticated limited access with no token
  // (local-only, no server auth yet established)
  if (!user.sessionToken && !user.userId) {
    return { ok: true, tier: "limited", licensed: false };
  }

  if (user.sessionToken !== token) return { ok: false, error: "invalid_token" };
  if (!isTokenValid(user)) return { ok: false, error: "token_expired" };

  return { ok: true, userId: user.userId, tier: user.tier, licensed: user.licensed && user.tier !== "limited" };
}

// Called after successful server auth — writes session locally
export function establishSession(params: {
  userId:             string;
  email:              string;
  tier:               UserTier;
  licenseKey:         string;
  telemetry?:         Partial<TelemetryConsent>;
  remoteAccessToken?: string;
  remoteRefreshToken?: string;
}): UserRecord {
  const existing = readUser();
  const token = generateToken();
  const now = Date.now();

  const accessTier = normalizeAccessTier(params.tier);
  const isLimitedAccess = accessTier === "limited";
  const telemetry: TelemetryConsent = {
    structural: true,
    logs: isLimitedAccess ? true : Boolean(params.telemetry?.logs ?? existing.telemetry?.logs ?? false),
    updatedAt: now
  };

  const user: UserRecord = {
    ...existing,
    userId:             params.userId,
    email:              params.email,
    tier:               accessTier,
    licensed:           accessTier !== "limited",
    licenseKey:         params.licenseKey,
    sessionToken:       token,
    tokenExpiresAt:     now + SESSION_TTL_MS,
    remoteAccessToken:  params.remoteAccessToken ?? existing.remoteAccessToken ?? "",
    remoteRefreshToken: params.remoteRefreshToken ?? existing.remoteRefreshToken ?? "",
    telemetry,
    installedAt:        existing.installedAt || now,
    lastAuthAt:         now
  };

  writeUser(user);

  try { fs.writeFileSync(TOKEN_PATH, token, "utf8"); } catch {}
  try {
    if (user.remoteAccessToken) fs.writeFileSync(REMOTE_ACCESS_PATH, user.remoteAccessToken, "utf8");
    else fs.unlinkSync(REMOTE_ACCESS_PATH);
  } catch {}
  try {
    if (user.remoteRefreshToken) fs.writeFileSync(REMOTE_REFRESH_PATH, user.remoteRefreshToken, "utf8");
    else fs.unlinkSync(REMOTE_REFRESH_PATH);
  } catch {}

  return user;
}

// Called on logout or license revocation
export function clearSession(): void {
  const user = readUser();
  writeUser({
    ...user,
    sessionToken:       "",
    tier:               "limited",
    licensed:           false,
    tokenExpiresAt:     0,
    remoteAccessToken:  "",
    remoteRefreshToken: "",
    lastAuthAt:         0
  });
  try { fs.unlinkSync(TOKEN_PATH); } catch {}
  try { fs.unlinkSync(REMOTE_ACCESS_PATH); } catch {}
  try { fs.unlinkSync(REMOTE_REFRESH_PATH); } catch {}
}

// Called by power.ts on boot to get cached token without a network call
export function getCachedToken(): string {
  try { return fs.readFileSync(TOKEN_PATH, "utf8").trim(); } catch { return ""; }
}

export function getRemoteAccessToken(): string {
  try { return fs.readFileSync(REMOTE_ACCESS_PATH, "utf8").trim(); }
  catch { return readUser().remoteAccessToken || ""; }
}

export function getRemoteRefreshToken(): string {
  try { return fs.readFileSync(REMOTE_REFRESH_PATH, "utf8").trim(); }
  catch { return readUser().remoteRefreshToken || ""; }
}

// Called when user updates telemetry preference. Limited access is not adjustable.
export function setTelemetryConsent(logs: boolean): TelemetryConsent {
  const user = readUser();
  if (user.tier === "limited") {
    return user.telemetry;
  }
  const telemetry: TelemetryConsent = {
    structural: true,
    logs,
    updatedAt: Date.now()
  };
  writeUser({ ...user, telemetry });
  return telemetry;
}

// Ensure user.json exists on first boot
export function ensureUserFile(): void {
  if (!fs.existsSync(USER_PATH)) {
    writeUser({ ...GUEST_USER, installedAt: Date.now() });
  }
}