/**
 * Structured logger facade (ADR 016).
 *
 * Phase 1: this is the default in-process implementation. Records are emitted
 * as JSON in production and pretty text in development. The shape matches the
 * OpenTelemetry log record so a Phase 3 swap to a real OTel SDK is mechanical.
 *
 * Phase 9: customer profiles may add an enterprise APM exporter alongside OTLP. The
 * calling code does not change — it imports `logger` from this file.
 *
 * Usage:
 *   import { logger } from "@/lib/logger";
 *   logger.info("provisioning.started", { jobId });
 *   logger.error("provisioning.failed", err, { jobId });
 *
 *   const reqLog = logger.child({ request_id: id, tenant_id: tenantId });
 *   reqLog.warn("rate.limited", { remaining: 0 });
 *
 * The pre-existing `logError(context, err)` helper is kept as a shim so
 * existing call sites continue to work unchanged.
 */

const LEVELS = ["debug", "info", "warn", "error"] as const;
type Level = (typeof LEVELS)[number];

const ENV_LEVEL = (process.env.LOG_LEVEL ?? (process.env.NODE_ENV === "production" ? "info" : "debug")).toLowerCase() as Level;
const SERVICE = process.env.LOG_SERVICE ?? "kadi-backend";
const ENV = process.env.NODE_ENV ?? "development";
const RELEASE = process.env.VERCEL_GIT_COMMIT_SHA ?? process.env.LOG_RELEASE ?? "dev";
const PRETTY = ENV !== "production";

const LEVEL_RANK: Record<Level, number> = { debug: 10, info: 20, warn: 30, error: 40 };
const MIN_RANK = LEVEL_RANK[LEVELS.includes(ENV_LEVEL) ? ENV_LEVEL : "info"];

/** Field names whose values are scrubbed before serialization. */
const SENSITIVE_KEYS = new Set([
  "password",
  "passcode",
  "secret",
  "token",
  "access_token",
  "refresh_token",
  "service_role_key",
  "service_role",
  "supabase_service_role_key",
  "authorization",
  "cookie",
  "set-cookie",
  "x-api-key",
  "api_key",
  "apikey",
  "session",
  "email",
  "user_email",
  "contact_email",
  "tempPassword",
  "temp_password",
  "magicLink",
  "magic_link",
  "action_link",
  // KAR-535: ASVS V13.9 — PII fields scrubbed at log-emit time.
  "first_name",
  "last_name",
  "full_name",
  "display_name",
  "phone",
  "phone_number",
  "mobile",
  "address",
  "street",
  "iban",
  "bic",
  "tax_id",
  "ssn",
  "date_of_birth",
  "dob",
]);

const REDACTED = "[REDACTED]";

export type LogContext = Record<string, unknown>;

interface LogRecord {
  timestamp: string;
  level: Level;
  message: string;
  service: string;
  env: string;
  release: string;
  trace_id?: string;
  span_id?: string;
  tenant_id?: string;
  user_id?: string;
  request_id?: string;
  /** Error details, if any. */
  err?: { name: string; message: string; stack?: string };
  /** Free-form structured context. */
  context?: LogContext;
}

function redact(value: unknown, depth = 0): unknown {
  if (depth > 6) return "[depth-cap]";
  if (value === null || value === undefined) return value;
  if (typeof value !== "object") return value;
  if (Array.isArray(value)) return value.map((v) => redact(v, depth + 1));
  const out: Record<string, unknown> = {};
  for (const [k, v] of Object.entries(value as Record<string, unknown>)) {
    if (SENSITIVE_KEYS.has(k.toLowerCase())) {
      out[k] = REDACTED;
      continue;
    }
    out[k] = redact(v, depth + 1);
  }
  return out;
}

function pickKnown(ctx: LogContext): Pick<LogRecord, "trace_id" | "span_id" | "tenant_id" | "user_id" | "request_id"> & { rest: LogContext } {
  const known: Pick<LogRecord, "trace_id" | "span_id" | "tenant_id" | "user_id" | "request_id"> = {};
  const rest: LogContext = {};
  for (const [k, v] of Object.entries(ctx)) {
    if (k === "trace_id" || k === "span_id" || k === "tenant_id" || k === "user_id" || k === "request_id") {
      if (v !== undefined && v !== null) known[k] = String(v);
    } else {
      rest[k] = v;
    }
  }
  return { ...known, rest };
}

function buildRecord(level: Level, message: string, ctx: LogContext, err?: unknown): LogRecord {
  const { rest, ...known } = pickKnown(ctx);
  const record: LogRecord = {
    timestamp: new Date().toISOString(),
    level,
    message,
    service: SERVICE,
    env: ENV,
    release: RELEASE,
    ...known,
  };
  if (err !== undefined) {
    if (err instanceof Error) {
      record.err = { name: err.name, message: err.message, stack: err.stack };
    } else {
      record.err = { name: "NonError", message: String(err) };
    }
  }
  if (Object.keys(rest).length > 0) record.context = redact(rest) as LogContext;
  return record;
}

function emit(record: LogRecord): void {
  // This is the ONE place where raw console.* is correct — the facade's
  // transport. Everything else in the app must go through `logger.*`.
  /* eslint-disable no-console */
  if (PRETTY) {
    const head = `${record.timestamp} ${record.level.toUpperCase().padEnd(5)} ${record.message}`;
    const recordMap = record as unknown as Record<string, unknown>;
    const known = ["trace_id", "span_id", "tenant_id", "user_id", "request_id"]
      .map((k) => recordMap[k] !== undefined ? `${k}=${String(recordMap[k])}` : "")
      .filter(Boolean)
      .join(" ");
    const ctx = record.context ? " " + JSON.stringify(record.context) : "";
    const tail = known ? ` (${known})` : "";
    const line = `${head}${tail}${ctx}`;
    if (record.level === "error") console.error(line);
    else if (record.level === "warn") console.warn(line);
    else console.log(line);
    if (record.err?.stack) console.error(record.err.stack);
    return;
  }
  // Production: single-line JSON to stdout/stderr.
  const line = JSON.stringify(record);
  if (record.level === "error") console.error(line);
  else if (record.level === "warn") console.warn(line);
  else console.log(line);
  /* eslint-enable no-console */
}

interface LoggerApi {
  debug(message: string, context?: LogContext): void;
  info(message: string, context?: LogContext): void;
  warn(message: string, context?: LogContext): void;
  error(message: string, err?: unknown, context?: LogContext): void;
  child(bindings: LogContext): LoggerApi;
}

function makeLogger(bindings: LogContext = {}): LoggerApi {
  function log(level: Level, message: string, context: LogContext = {}, err?: unknown) {
    if (LEVEL_RANK[level] < MIN_RANK) return;
    emit(buildRecord(level, message, { ...bindings, ...context }, err));
  }
  return {
    debug: (m, c) => log("debug", m, c),
    info: (m, c) => log("info", m, c),
    warn: (m, c) => log("warn", m, c),
    error: (m, err, c) => log("error", m, c, err),
    child: (extra) => makeLogger({ ...bindings, ...extra }),
  };
}

export const logger: LoggerApi = makeLogger();

/**
 * Backwards-compatible shim for the previous `logError(context, error)` API.
 * New call sites should use `logger.error(message, err, context)` directly.
 */
export function logError(context: string, error: unknown): void {
  logger.error(context, error);
}
