/**
 * AudioValidationPipe Tests
 *
 * Issue #398: Validates uploaded audio files for MIME type and file size.
 * Tests cover valid types, invalid types, size limits, and edge cases.
 */

import { describe, it, expect, beforeEach } from "vitest";
import { BadRequestException } from "@nestjs/common";
import { AudioValidationPipe } from "./audio-validation.pipe";

/**
 * Helper to create a mock Express.Multer.File object.
 */
function createMockFile(overrides: Partial<Express.Multer.File> = {}): Express.Multer.File {
  return {
    fieldname: "file",
    originalname: "test.mp3",
    encoding: "7bit",
    mimetype: "audio/mpeg",
    size: 1024,
    destination: "",
    filename: "",
    path: "",
    buffer: Buffer.from("fake-audio-data"),
    stream: undefined as never,
    ...overrides,
  };
}

describe("AudioValidationPipe", () => {
  // ==========================================
  // Default config (25MB max)
  // ==========================================
  describe("with default config", () => {
    let pipe: AudioValidationPipe;

    beforeEach(() => {
      pipe = new AudioValidationPipe();
    });

    // ==========================================
    // MIME type validation
    // ==========================================
    describe("MIME type validation", () => {
      it("should accept audio/wav", () => {
        const file = createMockFile({ mimetype: "audio/wav" });
        expect(pipe.transform(file)).toBe(file);
      });

      it("should accept audio/mp3", () => {
        const file = createMockFile({ mimetype: "audio/mp3" });
        expect(pipe.transform(file)).toBe(file);
      });

      it("should accept audio/mpeg", () => {
        const file = createMockFile({ mimetype: "audio/mpeg" });
        expect(pipe.transform(file)).toBe(file);
      });

      it("should accept audio/webm", () => {
        const file = createMockFile({ mimetype: "audio/webm" });
        expect(pipe.transform(file)).toBe(file);
      });

      it("should accept audio/ogg", () => {
        const file = createMockFile({ mimetype: "audio/ogg" });
        expect(pipe.transform(file)).toBe(file);
      });

      it("should accept audio/flac", () => {
        const file = createMockFile({ mimetype: "audio/flac" });
        expect(pipe.transform(file)).toBe(file);
      });

      it("should accept audio/x-m4a", () => {
        const file = createMockFile({ mimetype: "audio/x-m4a" });
        expect(pipe.transform(file)).toBe(file);
      });

      it("should reject unsupported MIME types with descriptive error", () => {
        const file = createMockFile({ mimetype: "video/mp4" });
        expect(() => pipe.transform(file)).toThrow(BadRequestException);
        expect(() => pipe.transform(file)).toThrow(/Unsupported audio format.*video\/mp4/);
      });

      it("should reject application/octet-stream", () => {
        const file = createMockFile({ mimetype: "application/octet-stream" });
        expect(() => pipe.transform(file)).toThrow(BadRequestException);
      });

      it("should reject text/plain", () => {
        const file = createMockFile({ mimetype: "text/plain" });
        expect(() => pipe.transform(file)).toThrow(BadRequestException);
      });

      it("should reject image/png", () => {
        const file = createMockFile({ mimetype: "image/png" });
        expect(() => pipe.transform(file)).toThrow(BadRequestException);
      });

      it("should include supported formats in error message", () => {
        const file = createMockFile({ mimetype: "video/mp4" });
        try {
          pipe.transform(file);
          expect.fail("Expected BadRequestException");
        } catch (error) {
          expect(error).toBeInstanceOf(BadRequestException);
          const response = (error as BadRequestException).getResponse();
          const message =
            typeof response === "string" ? response : (response as Record<string, unknown>).message;
          expect(message).toContain("audio/wav");
          expect(message).toContain("audio/mpeg");
        }
      });
    });

    // ==========================================
    // File size validation
    // ==========================================
    describe("file size validation", () => {
      it("should accept files under the size limit", () => {
        const file = createMockFile({ size: 1024 * 1024 }); // 1MB
        expect(pipe.transform(file)).toBe(file);
      });

      it("should accept files exactly at the size limit", () => {
        const file = createMockFile({ size: 25_000_000 }); // 25MB (default)
        expect(pipe.transform(file)).toBe(file);
      });

      it("should reject files exceeding the size limit", () => {
        const file = createMockFile({ size: 25_000_001 }); // 1 byte over
        expect(() => pipe.transform(file)).toThrow(BadRequestException);
        expect(() => pipe.transform(file)).toThrow(/exceeds maximum/);
      });

      it("should include human-readable sizes in error message", () => {
        const file = createMockFile({ size: 30_000_000 }); // 30MB
        try {
          pipe.transform(file);
          expect.fail("Expected BadRequestException");
        } catch (error) {
          expect(error).toBeInstanceOf(BadRequestException);
          const response = (error as BadRequestException).getResponse();
          const message =
            typeof response === "string" ? response : (response as Record<string, unknown>).message;
          // Should show something like "28.6 MB" and "23.8 MB"
          expect(message).toContain("MB");
        }
      });

      it("should accept zero-size files (MIME check still applies)", () => {
        const file = createMockFile({ size: 0 });
        expect(pipe.transform(file)).toBe(file);
      });
    });

    // ==========================================
    // Edge cases
    // ==========================================
    describe("edge cases", () => {
      it("should throw if no file is provided (null)", () => {
        expect(() => pipe.transform(null as unknown as Express.Multer.File)).toThrow(
          BadRequestException
        );
        expect(() => pipe.transform(null as unknown as Express.Multer.File)).toThrow(
          /No audio file provided/
        );
      });

      it("should throw if no file is provided (undefined)", () => {
        expect(() => pipe.transform(undefined as unknown as Express.Multer.File)).toThrow(
          BadRequestException
        );
      });
    });
  });

  // ==========================================
  // Custom config
  // ==========================================
  describe("with custom config", () => {
    it("should use custom max file size", () => {
      const pipe = new AudioValidationPipe({ maxFileSize: 1_000_000 }); // 1MB
      const smallFile = createMockFile({ size: 500_000 });
      expect(pipe.transform(smallFile)).toBe(smallFile);

      const largeFile = createMockFile({ size: 1_000_001 });
      expect(() => pipe.transform(largeFile)).toThrow(BadRequestException);
    });

    it("should allow overriding accepted MIME types", () => {
      const pipe = new AudioValidationPipe({
        allowedMimeTypes: ["audio/wav"],
      });

      const wavFile = createMockFile({ mimetype: "audio/wav" });
      expect(pipe.transform(wavFile)).toBe(wavFile);

      const mp3File = createMockFile({ mimetype: "audio/mpeg" });
      expect(() => pipe.transform(mp3File)).toThrow(BadRequestException);
    });
  });
});
