import { mkdir, readFile, rename, writeFile } from "node:fs/promises";
import { dirname } from "node:path";
import type { SearchHistory, SearchHistoryResponse } from "../shared/contract.js";

interface StoredHistory {
  updatedAt: number;
  history: SearchHistory;
}

export class JsonSearchHistoryStorage {
  constructor(private readonly filePath: string) {}

  async get(): Promise<StoredHistory | null> {
    try {
      const value = JSON.parse(await readFile(this.filePath, "utf8")) as unknown;
      if (!isStoredHistory(value)) throw new Error("invalid search-history file");
      return value;
    } catch (error) {
      if (isMissingFile(error)) return null;
      throw error;
    }
  }

  async set(history: SearchHistory, updatedAt = Date.now()): Promise<number> {
    const tmp = `${this.filePath}.tmp`;
    await mkdir(dirname(this.filePath), { recursive: true });
    await writeFile(tmp, `${JSON.stringify({ history, updatedAt }, null, 2)}\n`, { mode: 0o600 });
    await rename(tmp, this.filePath);
    return updatedAt;
  }

  async loadResponse(): Promise<SearchHistoryResponse> {
    const stored = await this.get();
    return stored ?? { history: null, updatedAt: null };
  }
}

function isStoredHistory(value: unknown): value is StoredHistory {
  if (!value || typeof value !== "object") return false;
  const candidate = value as Partial<StoredHistory>;
  return Number.isFinite(candidate.updatedAt) && !!candidate.history && typeof candidate.history === "object";
}

function isMissingFile(error: unknown): boolean {
  return !!error && typeof error === "object" && "code" in error && error.code === "ENOENT";
}
