import fs from "node:fs/promises";
import path from "node:path";
import { config } from "../config"
import { DbSchema } from "../types"
import { Mutex } from "./mutex"

const mutex = new Mutex();

const defaultDb = (): DbSchema => ({
  users: [],
  settings: {
    pollIntervalMs: config.pollIntervalMs,
    enforceIntervalMs: config.enforceIntervalMs,
    defaultDelayMs: config.defaultDelayMs,
    maxLoopLimit: config.maxLoopLimit,
  },
  loopJobs: [],
  profiles: [],
  auditLogs: [],
  archives: {},
  timerRules: [],
  timerLogs: [],
  timerSummary: {
    totalDeleted: 0,
    totalSeededSeconds: 0,
    totalUploadedBytes: 0,
    updatedAt: new Date().toISOString(),
  },
  watchers: [],
  watcherItems: [],
  watcherRuns: [],
  watcherSummary: {
    activeWatchers: 0,
    totalImported: 0,
    totalSeen: 0,
    trackedLabels: [],
    updatedAt: new Date().toISOString(),
  },
});

const rotateBackups = async (dbPath: string) => {
  const dir = path.dirname(dbPath);
  const base = path.basename(dbPath);
  for (let i = 2; i >= 0; i -= 1) {
    const src = path.join(dir, `${base}.bak${i}`);
    const dest = path.join(dir, `${base}.bak${i + 1}`);
    try {
      await fs.rename(src, dest);
    } catch (error) {
      if ((error as NodeJS.ErrnoException).code !== "ENOENT") {
        throw error;
      }
    }
  }
  try {
    await fs.copyFile(dbPath, path.join(dir, `${base}.bak0`));
  } catch (error) {
    if ((error as NodeJS.ErrnoException).code !== "ENOENT") {
      throw error;
    }
  }
};

const writeRaw = async (data: DbSchema) => {
  await rotateBackups(config.dbPath);
  const tempPath = `${config.dbPath}.tmp`;
  await fs.writeFile(tempPath, JSON.stringify(data, null, 2), "utf-8");
  await fs.rename(tempPath, config.dbPath);
};

export const readDb = async (): Promise<DbSchema> => {
  return mutex.run(async () => {
    try {
      const content = await fs.readFile(config.dbPath, "utf-8");
      const parsed = JSON.parse(content) as DbSchema;
      parsed.timerRules ??= [];
      parsed.timerLogs ??= [];
      parsed.timerSummary ??= {
        totalDeleted: 0,
        totalSeededSeconds: 0,
        totalUploadedBytes: 0,
        updatedAt: new Date().toISOString(),
      };
      parsed.watchers ??= [];
      parsed.watcherItems ??= [];
      parsed.watcherRuns ??= [];
      parsed.watcherSummary ??= {
        activeWatchers: 0,
        totalImported: 0,
        totalSeen: 0,
        trackedLabels: [],
        updatedAt: new Date().toISOString(),
      };
      return parsed;
    } catch (error) {
      if ((error as NodeJS.ErrnoException).code !== "ENOENT") {
        throw error;
      }
      const initial = defaultDb();
      await writeRaw(initial);
      return initial;
    }
  });
};

export const writeDb = async (data: DbSchema): Promise<void> => {
  await mutex.run(async () => {
    await writeRaw(data);
  });
};
