import {
  writeFileSync,
  mkdirSync,
  readFileSync,
  unlinkSync,
  existsSync,
  renameSync,
  copyFileSync,
} from "node:fs";
import { join, isAbsolute } from "node:path";
import { logger } from "./logger";
import { z } from "zod";
import { JsonValueSchema, type StreamingChunk } from "./schemas";

export type StreamingChunksData = {
  chunks: StreamingChunk[];
  truncated?: boolean;
};

const StreamingChunkSchema: z.ZodType<StreamingChunk> = z.object({
  index: z.number(),
  timestamp: z.number(),
  type: z.string(),
  data: JsonValueSchema,
});

const StreamingChunksDataSchema = z.object({
  chunks: z.array(StreamingChunkSchema),
  truncated: z.boolean().optional(),
});

const CHUNKS_DIR_ENV = process.env["CHUNKS_DIR"];

export function getChunksDir(): string {
  const isWindows = process.platform === "win32";
  const base = isWindows
    ? (process.env["APPDATA"] ?? join(process.env["USERPROFILE"] ?? "C:\\", ".llm-inspector"))
    : (process.env["HOME"] ?? "/tmp");

  if (CHUNKS_DIR_ENV !== undefined) {
    return isAbsolute(CHUNKS_DIR_ENV) ? CHUNKS_DIR_ENV : join(base, CHUNKS_DIR_ENV);
  }
  return join(base, ".llm-inspector", "chunks");
}

function getChunkFilePath(logId: number): string {
  return join(getChunksDir(), `${logId}.json`);
}

function getTempFilePath(logId: number): string {
  return join(getChunksDir(), `.${logId}.tmp`);
}

/**
 * Write chunks to disk atomically: write to temp file first, then rename.
 * Returns the absolute path to the written chunk file.
 */
export function writeChunks(logId: number, chunks: StreamingChunk[], truncated?: boolean): string {
  const dir = getChunksDir();
  const targetPath = getChunkFilePath(logId);
  const tempPath = getTempFilePath(logId);

  // Ensure directory exists
  try {
    mkdirSync(dir, { recursive: true });
  } catch (err) {
    logger.error("[chunkStorage] Failed to create chunks directory:", String(err));
  }

  const data: StreamingChunksData = { chunks, truncated };

  // Write to temp file first
  try {
    writeFileSync(tempPath, JSON.stringify(data), "utf-8");
  } catch (err) {
    logger.error("[chunkStorage] Failed to write chunks temp file:", String(err));
    return targetPath;
  }

  // Rename to target (atomic on most filesystems)
  try {
    renameSync(tempPath, targetPath);
  } catch (err) {
    // eslint-disable-next-line no-console
    console.warn("[chunkStorage] Rename failed, falling back to copy+delete:", err);
    // If rename fails, try copy + delete
    try {
      copyFileSync(tempPath, targetPath);
      unlinkSync(tempPath);
    } catch (copyErr) {
      logger.error("[chunkStorage] Failed to copy chunks file:", String(copyErr));
    }
  }

  return targetPath;
}

/**
 * Read chunks from a chunk file path.
 * Returns null if the file does not exist or cannot be read.
 */
export function readChunks(path: string): StreamingChunksData | null {
  if (!existsSync(path)) {
    return null;
  }
  try {
    const content = readFileSync(path, "utf-8");
    // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
    const parsed = JSON.parse(content);
    const result = StreamingChunksDataSchema.safeParse(parsed);
    if (!result.success) return null;
    return result.data;
  } catch {
    return null;
  }
}
