import { createCipheriv, createDecipheriv, randomBytes, timingSafeEqual } from "node:crypto";
import { mkdir, readFile, rename, rm, writeFile } from "node:fs/promises";
import path from "node:path";
import type { SessionPayload } from "../types.ts";

const MAGIC = Buffer.from("FILYX02", "ascii");

export class SessionVault {
  readonly filePath: string;
  private readonly key: Buffer;

  constructor(filePath: string, masterKey: string) {
    this.filePath = filePath;
    this.key = SessionVault.decodeKey(masterKey);
  }

  async save(payload: SessionPayload): Promise<void> {
    if (
      payload.accountUsername !== "fiIyx"
      || payload.provider !== "kick"
      || payload.version !== 2
      || !payload.accessToken
      || !payload.refreshToken
    ) {
      throw new Error("Jetons OAuth Kick refusés.");
    }
    const nonce = randomBytes(12);
    const cipher = createCipheriv("aes-256-gcm", this.key, nonce);
    cipher.setAAD(MAGIC);
    const plaintext = Buffer.from(JSON.stringify(payload), "utf8");
    const encrypted = Buffer.concat([cipher.update(plaintext), cipher.final()]);
    const tag = cipher.getAuthTag();
    const file = Buffer.concat([MAGIC, nonce, tag, encrypted]);
    await mkdir(path.dirname(this.filePath), { recursive: true });
    const temporary = `${this.filePath}.${process.pid}.tmp`;
    await writeFile(temporary, file, { mode: 0o600 });
    await rename(temporary, this.filePath);
  }

  async load(): Promise<SessionPayload> {
    const file = await readFile(this.filePath);
    if (file.length < MAGIC.length + 12 + 16 || !timingSafeEqual(file.subarray(0, MAGIC.length), MAGIC)) {
      throw new Error("Format du coffre FilyX invalide.");
    }
    const nonceStart = MAGIC.length;
    const nonce = file.subarray(nonceStart, nonceStart + 12);
    const tag = file.subarray(nonceStart + 12, nonceStart + 28);
    const encrypted = file.subarray(nonceStart + 28);
    try {
      const decipher = createDecipheriv("aes-256-gcm", this.key, nonce);
      decipher.setAAD(MAGIC);
      decipher.setAuthTag(tag);
      const plaintext = Buffer.concat([decipher.update(encrypted), decipher.final()]);
      const payload = JSON.parse(plaintext.toString("utf8")) as SessionPayload;
      if (
        payload.version !== 2
        || payload.provider !== "kick"
        || payload.accountUsername !== "fiIyx"
        || !payload.accessToken
        || !payload.refreshToken
        || !Array.isArray(payload.scopes)
      ) {
        throw new Error("Autorisation OAuth d’un compte non autorisé.");
      }
      return payload;
    } catch (error) {
      throw new Error("Clé incorrecte ou coffre OAuth corrompu.", { cause: error });
    }
  }

  async revoke(): Promise<void> {
    await rm(this.filePath, { force: true });
  }

  static decodeKey(value: string): Buffer {
    const trimmed = value.trim();
    const key = /^[a-f0-9]{64}$/i.test(trimmed)
      ? Buffer.from(trimmed, "hex")
      : Buffer.from(trimmed, "base64");
    if (key.length !== 32) {
      throw new Error("FILYX_OAUTH_MASTER_KEY doit contenir exactement 32 octets (hex ou base64).");
    }
    return key;
  }
}
