import { describe, it, expect, beforeEach } from "vitest";
import { ConfigService } from "@nestjs/config";
import { CryptoService } from "./crypto.service";

function createConfigService(secret?: string): ConfigService {
  return {
    get: (key: string) => {
      if (key === "MOSAIC_SECRET_KEY") {
        return secret;
      }
      return undefined;
    },
  } as unknown as ConfigService;
}

describe("CryptoService", () => {
  let service: CryptoService;

  beforeEach(() => {
    service = new CryptoService(createConfigService("this-is-a-test-secret-key-with-32+chars"));
  });

  it("encrypt -> decrypt roundtrip", () => {
    const plaintext = "my-secret-api-key";

    const encrypted = service.encrypt(plaintext);
    const decrypted = service.decrypt(encrypted);

    expect(encrypted.startsWith("enc:")).toBe(true);
    expect(decrypted).toBe(plaintext);
  });

  it("decrypt rejects tampered ciphertext", () => {
    const encrypted = service.encrypt("sensitive-token");
    const payload = encrypted.slice(4);
    const bytes = Buffer.from(payload, "base64");

    bytes[bytes.length - 1] = bytes[bytes.length - 1]! ^ 0xff;

    const tampered = `enc:${bytes.toString("base64")}`;

    expect(() => service.decrypt(tampered)).toThrow();
  });

  it("decrypt rejects non-encrypted string", () => {
    expect(() => service.decrypt("plain-text-value")).toThrow();
  });

  it("isEncrypted detects prefix correctly", () => {
    expect(service.isEncrypted("enc:abc")).toBe(true);
    expect(service.isEncrypted("ENC:abc")).toBe(false);
    expect(service.isEncrypted("plain-text")).toBe(false);
  });

  it("generateToken returns 64-char hex string", () => {
    const token = service.generateToken();

    expect(token).toMatch(/^[0-9a-f]{64}$/);
  });

  it("different plaintexts produce different ciphertexts (random IV)", () => {
    const encryptedA = service.encrypt("value-a");
    const encryptedB = service.encrypt("value-b");

    expect(encryptedA).not.toBe(encryptedB);
  });

  it("missing MOSAIC_SECRET_KEY throws on construction", () => {
    expect(() => new CryptoService(createConfigService(undefined))).toThrow();
  });
});
