import { randomUUID } from "node:crypto";
import { mkdirSync, readFileSync, readdirSync, statSync, utimesSync, writeFileSync } from "node:fs";
import { join } from "node:path";
import { tmpdir } from "node:os";

import { afterEach, describe, expect, it } from "vitest";

import { ManagedStorage } from "../../apps/api/src/managed-storage.js";
import {
  LOG_FILE_LIMIT,
  LOG_RETENTION_DAYS,
  LOG_SIZE_LIMIT_BYTES,
  StructuredJsonlLogger,
  sanitizeDiagnosticRecord,
} from "../../apps/api/src/structured-log.js";

const temporaryRoots: string[] = [];

function fixtureRoot() {
  const root = join(tmpdir(), `dada-wp0-08-${randomUUID()}`);
  mkdirSync(root, { recursive: true });
  temporaryRoots.push(root);
  return root;
}

function evidence(name: string, value: unknown) {
  const directory = process.env.DADA_EVIDENCE_DIR_LOG;
  if (!directory) return;
  mkdirSync(directory, { recursive: true });
  writeFileSync(join(directory, name), `${JSON.stringify(value, null, 2)}\n`);
}

afterEach(async () => {
  const { rm } = await import("node:fs/promises");
  await Promise.all(temporaryRoots.splice(0).map((root) => rm(root, { force: true, recursive: true })));
});

describe("TDD-WP0-LOG-001 rotation and redaction", () => {
  it("enforces 10 MiB, 10 files, 30 days and emits only allowlisted JSONL fields", () => {
    const root = fixtureRoot();
    const logDirectory = join(root, "logs", "api");
    mkdirSync(logDirectory, { recursive: true });
    const activePath = join(logDirectory, "api.jsonl");
    const seedLine = `${JSON.stringify({
      component: "api",
      correlation_id: "corr_fixture",
      schema_version: "1.0",
      status_category: "ready",
      timestamp: "2026-01-01T00:00:00.000Z",
    })}\n`;
    const repeats = Math.floor(LOG_SIZE_LIMIT_BYTES / Buffer.byteLength(seedLine));
    writeFileSync(activePath, seedLine.repeat(repeats));
    const bytesBeforeBoundaryWrite = statSync(activePath).size;

    const marker = `trap-${randomUUID()}`;
    const logger = new StructuredJsonlLogger({ component: "api", directory: logDirectory });
    logger.write({
      absolute_path: join(root, marker),
      api_key: `${marker}-key`,
      correlation_id: "corr_boundary",
      duration_ms: 12,
      email: `${marker}@example.invalid`,
      error_category: "none",
      image: Buffer.from(marker),
      object_id: "obj_boundary",
      prompt: `${marker} private prompt`,
      provider_error: `${marker} upstream body`,
      status_category: "completed",
    });
    expect(statSync(join(logDirectory, "api.1.jsonl")).size).toBeLessThanOrEqual(LOG_SIZE_LIMIT_BYTES);
    expect(statSync(activePath).size).toBeLessThanOrEqual(LOG_SIZE_LIMIT_BYTES);

    for (let index = 2; index <= 14; index += 1) writeFileSync(join(logDirectory, `api.${index}.jsonl`), seedLine);
    const stalePath = join(logDirectory, "api.4.jsonl");
    const stale = new Date(Date.now() - (LOG_RETENTION_DAYS + 1) * 24 * 60 * 60 * 1000);
    utimesSync(stalePath, stale, stale);
    new StructuredJsonlLogger({ component: "api", directory: logDirectory }).maintain();

    const files = readdirSync(logDirectory).sort();
    const scanned = files.map((name) => readFileSync(join(logDirectory, name), "utf8")).join("");
    expect(files.length).toBeLessThanOrEqual(LOG_FILE_LIMIT);
    expect(files).not.toContain("api.4.jsonl");
    expect(scanned).not.toContain(marker);
    const activeEntry = JSON.parse(readFileSync(activePath, "utf8").trim()) as Record<string, unknown>;
    expect(Object.keys(activeEntry).sort()).toEqual([
      "component", "correlation_id", "duration_ms", "error_category", "object_id", "schema_version", "status_category", "timestamp",
    ]);

    const diagnostic = sanitizeDiagnosticRecord({
      app_version: "0.0.0",
      checked_at: "2026-01-01T00:00:00.000Z",
      component: "api",
      message_key: "log_write_failed",
      private_content: marker,
      result: "fail",
      stable_check_code: "log_writable",
      user_path: join(root, marker),
    });
    expect(JSON.stringify(diagnostic)).not.toContain(marker);
    evidence("log-manifest.json", {
      bytes_before_boundary_write: bytesBeforeBoundaryWrite,
      file_limit: LOG_FILE_LIMIT,
      files: files.map((name) => ({ name, size: statSync(join(logDirectory, name)).size })),
      retention_days: LOG_RETENTION_DAYS,
      size_limit_bytes: LOG_SIZE_LIMIT_BYTES,
      status: "passed",
      total_generated_bytes: bytesBeforeBoundaryWrite + statSync(activePath).size,
    });
    evidence("redaction.json", { diagnostic, marker_absent: true, status: "passed" });
  });

  it("marks storage unavailable when the log directory cannot be written", () => {
    const root = fixtureRoot();
    const storage = new ManagedStorage({ dataRoot: root, databasePath: join(root, "db", "dada.sqlite3") });
    const blockedDirectory = join(root, "logs-blocked");
    writeFileSync(blockedDirectory, "not-a-directory");
    const logger = new StructuredJsonlLogger({
      component: "api",
      directory: blockedDirectory,
      onWriteFailure: () => storage.setLogAvailability(false),
    });
    expect(() => logger.write({ correlation_id: "corr_log_failure", error_category: "log_write_failed", status_category: "failed" })).toThrow("log_write_failed");
    expect(storage.getState().storage_status).toBe("unavailable");
    expect(storage.inspectAction("ai_call")).toBe("reject_unavailable");
    evidence("response.json", {
      ai_call: storage.inspectAction("ai_call"),
      business_objects_changed: 0,
      storage_status: storage.getState().storage_status,
      status: "passed",
    });
    storage.setLogAvailability(true);
    expect(storage.getState().storage_status).toBe("unavailable");
    storage.applyControlledMeasurement(0);
    expect(storage.getState().storage_status).toBe("active");
    storage.close();
  });
});
