import { describe, it, expect } from "vitest";

/**
 * CORS Configuration Tests
 *
 * These tests verify that CORS is configured correctly for cookie-based authentication.
 *
 * CRITICAL REQUIREMENTS:
 * - credentials: true (allows cookies to be sent)
 * - origin: must be specific origins, NOT wildcard (security requirement with credentials)
 * - Access-Control-Allow-Credentials: true header
 * - Access-Control-Allow-Origin: specific origin (not *)
 * - No-origin requests blocked in production (SEC-API-26)
 */

/**
 * Replicates the CORS origin validation logic from main.ts
 * so we can test it in isolation.
 */
function buildOriginValidator(nodeEnv: string | undefined): {
  allowedOrigins: string[];
  isDevelopment: boolean;
  validate: (
    origin: string | undefined,
    callback: (err: Error | null, allow?: boolean) => void
  ) => void;
} {
  const isDevelopment = nodeEnv !== "production";

  const allowedOrigins = [
    process.env.NEXT_PUBLIC_APP_URL ?? "http://localhost:3000",
    "https://app.mosaicstack.dev",
    "https://api.mosaicstack.dev",
  ];

  if (isDevelopment) {
    allowedOrigins.push("http://localhost:3001");
  }

  const validate = (
    origin: string | undefined,
    callback: (err: Error | null, allow?: boolean) => void
  ): void => {
    if (!origin) {
      if (isDevelopment) {
        callback(null, true);
      } else {
        callback(new Error("CORS: Origin header is required"));
      }
      return;
    }

    if (allowedOrigins.includes(origin)) {
      callback(null, true);
    } else {
      callback(new Error(`Origin ${origin} not allowed by CORS`));
    }
  };

  return { allowedOrigins, isDevelopment, validate };
}

describe("CORS Configuration", () => {
  describe("Configuration requirements", () => {
    it("should document required CORS settings for cookie-based auth", () => {
      const requiredSettings = {
        origin: ["http://localhost:3000", "https://app.mosaicstack.dev"],
        credentials: true,
        allowedHeaders: ["Content-Type", "Authorization", "Cookie"],
        exposedHeaders: ["Set-Cookie"],
        methods: ["GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"],
      };

      expect(requiredSettings.credentials).toBe(true);
      expect(requiredSettings.origin).not.toContain("*");
      expect(requiredSettings.allowedHeaders).toContain("Cookie");
    });

    it("should NOT use wildcard origin with credentials (security violation)", () => {
      const validConfig1 = { origin: "*", credentials: false };
      const validConfig2 = { origin: "http://localhost:3000", credentials: true };
      const invalidConfig = { origin: "*", credentials: true };

      expect(validConfig1.origin === "*" && !validConfig1.credentials).toBe(true);
      expect(validConfig2.origin !== "*" && validConfig2.credentials).toBe(true);

      const isInvalidCombination = invalidConfig.origin === "*" && invalidConfig.credentials;
      expect(isInvalidCombination).toBe(true);
    });
  });

  describe("Origin validation", () => {
    it("should define allowed origins list", () => {
      const { allowedOrigins } = buildOriginValidator("development");

      expect(allowedOrigins).toContain("http://localhost:3000");
      expect(allowedOrigins).toContain("https://app.mosaicstack.dev");
      expect(allowedOrigins).toContain("https://api.mosaicstack.dev");
    });

    it("should match exact origins, not partial matches", () => {
      const origin = "http://localhost:3000";
      const maliciousOrigin = "http://localhost:3000.evil.com";

      expect(origin).toBe("http://localhost:3000");
      expect(maliciousOrigin).not.toBe(origin);
    });

    it("should support dynamic origin from environment variable", () => {
      const defaultOrigin = "http://localhost:3000";
      const envOrigin = process.env.NEXT_PUBLIC_APP_URL ?? defaultOrigin;

      expect(envOrigin).toBeDefined();
      expect(typeof envOrigin).toBe("string");
    });
  });

  describe("Development mode CORS behavior", () => {
    it("should allow requests with no origin in development", () => {
      const { validate } = buildOriginValidator("development");

      return new Promise<void>((resolve) => {
        validate(undefined, (err, allow) => {
          expect(err).toBeNull();
          expect(allow).toBe(true);
          resolve();
        });
      });
    });

    it("should include localhost:3001 in development origins", () => {
      const { allowedOrigins } = buildOriginValidator("development");

      expect(allowedOrigins).toContain("http://localhost:3001");
    });

    it("should allow valid origins in development", () => {
      const { validate } = buildOriginValidator("development");

      return new Promise<void>((resolve) => {
        validate("http://localhost:3000", (err, allow) => {
          expect(err).toBeNull();
          expect(allow).toBe(true);
          resolve();
        });
      });
    });

    it("should reject invalid origins in development", () => {
      const { validate } = buildOriginValidator("development");

      return new Promise<void>((resolve) => {
        validate("http://evil.com", (err) => {
          expect(err).toBeInstanceOf(Error);
          expect(err?.message).toContain("not allowed by CORS");
          resolve();
        });
      });
    });
  });

  describe("Production mode CORS behavior (SEC-API-26)", () => {
    it("should reject requests with no origin in production", () => {
      const { validate } = buildOriginValidator("production");

      return new Promise<void>((resolve) => {
        validate(undefined, (err) => {
          expect(err).toBeInstanceOf(Error);
          expect(err?.message).toBe("CORS: Origin header is required");
          resolve();
        });
      });
    });

    it("should NOT include localhost:3001 in production origins", () => {
      const { allowedOrigins } = buildOriginValidator("production");

      expect(allowedOrigins).not.toContain("http://localhost:3001");
    });

    it("should allow valid production origins", () => {
      const { validate } = buildOriginValidator("production");

      return new Promise<void>((resolve) => {
        validate("https://app.mosaicstack.dev", (err, allow) => {
          expect(err).toBeNull();
          expect(allow).toBe(true);
          resolve();
        });
      });
    });

    it("should reject invalid origins in production", () => {
      const { validate } = buildOriginValidator("production");

      return new Promise<void>((resolve) => {
        validate("http://evil.com", (err) => {
          expect(err).toBeInstanceOf(Error);
          expect(err?.message).toContain("not allowed by CORS");
          resolve();
        });
      });
    });

    it("should reject malicious origins that try partial matching", () => {
      const { validate } = buildOriginValidator("production");

      return new Promise<void>((resolve) => {
        validate("https://app.mosaicstack.dev.evil.com", (err) => {
          expect(err).toBeInstanceOf(Error);
          expect(err?.message).toContain("not allowed by CORS");
          resolve();
        });
      });
    });
  });

  describe("ValidationPipe strict mode (SEC-API-25)", () => {
    it("should document that forbidNonWhitelisted must be true", () => {
      // This verifies the configuration intent:
      // forbidNonWhitelisted: true rejects requests with unknown properties
      // preventing mass-assignment vulnerabilities
      const validationPipeConfig = {
        transform: true,
        whitelist: true,
        forbidNonWhitelisted: true,
        transformOptions: {
          enableImplicitConversion: false,
        },
      };

      expect(validationPipeConfig.forbidNonWhitelisted).toBe(true);
      expect(validationPipeConfig.whitelist).toBe(true);
      expect(validationPipeConfig.transformOptions.enableImplicitConversion).toBe(false);
    });
  });
});
