import { safeStorage } from "electron";
import type { ICachePlugin, TokenCacheContext } from "@azure/msal-node";
import { mkdir, readFile, rename, unlink, writeFile } from "node:fs/promises";
import { dirname } from "node:path";
import type { LauncherLogger } from "./logger.js";

async function removeIfPresent(path: string): Promise<void> {
  try {
    await unlink(path);
  } catch (error) {
    if ((error as NodeJS.ErrnoException).code !== "ENOENT") throw error;
  }
}

/** MSAL cache persisted only through the operating-system protected storage. */
export class SecureMsalCachePlugin implements ICachePlugin {
  constructor(
    private readonly path: string,
    private readonly logger: LauncherLogger,
  ) {}

  async beforeCacheAccess(context: TokenCacheContext): Promise<void> {
    if (!safeStorage.isEncryptionAvailable()) return;
    try {
      const encrypted = await readFile(this.path);
      const plaintext = safeStorage.decryptString(encrypted);
      context.tokenCache.deserialize(plaintext);
    } catch (error) {
      if ((error as NodeJS.ErrnoException).code !== "ENOENT") {
        await this.logger.warn("Cache Microsoft illisible; une reconnexion sera demandée", error);
        await removeIfPresent(this.path);
      }
    }
  }

  async afterCacheAccess(context: TokenCacheContext): Promise<void> {
    if (!context.cacheHasChanged) return;
    if (!safeStorage.isEncryptionAvailable()) {
      await this.logger.warn("Chiffrement système indisponible; aucun jeton Microsoft n'est écrit sur disque");
      return;
    }
    await mkdir(dirname(this.path), { recursive: true, mode: 0o700 });
    const temporary = `${this.path}.tmp-${process.pid}`;
    const encrypted = safeStorage.encryptString(context.tokenCache.serialize());
    await writeFile(temporary, encrypted, { flag: "wx", mode: 0o600 });
    await rename(temporary, this.path);
  }

  clear(): Promise<void> {
    return removeIfPresent(this.path);
  }
}
