/**
 * CSRF Controller Tests
 *
 * Tests CSRF token generation endpoint with session binding.
 */

import { describe, it, expect, beforeEach, afterEach, vi } from "vitest";
import { Request, Response } from "express";
import { CsrfController } from "./csrf.controller";
import { CsrfService } from "../services/csrf.service";
import type { AuthenticatedUser } from "../types/user.types";

interface AuthenticatedRequest extends Request {
  user?: AuthenticatedUser;
}

describe("CsrfController", () => {
  let controller: CsrfController;
  let csrfService: CsrfService;
  const originalEnv = process.env;

  beforeEach(() => {
    process.env = { ...originalEnv };
    process.env.CSRF_SECRET = "test-secret-0123456789abcdef0123456789abcdef";
    csrfService = new CsrfService();
    csrfService.onModuleInit();
    controller = new CsrfController(csrfService);
  });

  afterEach(() => {
    process.env = originalEnv;
  });

  const createMockRequest = (userId?: string): AuthenticatedRequest => {
    return {
      user: userId ? { id: userId, email: "test@example.com", name: "Test User" } : undefined,
    } as AuthenticatedRequest;
  };

  const createMockResponse = (): Response => {
    return {
      cookie: vi.fn(),
    } as unknown as Response;
  };

  describe("getCsrfToken", () => {
    it("should generate and return a CSRF token with session binding", () => {
      const mockRequest = createMockRequest("user-123");
      const mockResponse = createMockResponse();

      const result = controller.getCsrfToken(mockRequest, mockResponse);

      expect(result).toHaveProperty("token");
      expect(typeof result.token).toBe("string");
      // Token format: random:hmac (64 hex chars : 64 hex chars)
      expect(result.token).toContain(":");
      const parts = result.token.split(":");
      expect(parts[0]).toHaveLength(64);
      expect(parts[1]).toHaveLength(64);
    });

    it("should set CSRF token in httpOnly cookie", () => {
      const mockRequest = createMockRequest("user-123");
      const mockResponse = createMockResponse();

      const result = controller.getCsrfToken(mockRequest, mockResponse);

      expect(mockResponse.cookie).toHaveBeenCalledWith(
        "csrf-token",
        result.token,
        expect.objectContaining({
          httpOnly: true,
          sameSite: "strict",
        })
      );
    });

    it("should set secure flag in production", () => {
      process.env.NODE_ENV = "production";

      const mockRequest = createMockRequest("user-123");
      const mockResponse = createMockResponse();

      controller.getCsrfToken(mockRequest, mockResponse);

      expect(mockResponse.cookie).toHaveBeenCalledWith(
        "csrf-token",
        expect.any(String),
        expect.objectContaining({
          secure: true,
        })
      );
    });

    it("should not set secure flag in development", () => {
      process.env.NODE_ENV = "development";

      const mockRequest = createMockRequest("user-123");
      const mockResponse = createMockResponse();

      controller.getCsrfToken(mockRequest, mockResponse);

      expect(mockResponse.cookie).toHaveBeenCalledWith(
        "csrf-token",
        expect.any(String),
        expect.objectContaining({
          secure: false,
        })
      );
    });

    it("should generate unique tokens on each call", () => {
      const mockRequest = createMockRequest("user-123");
      const mockResponse = createMockResponse();

      const result1 = controller.getCsrfToken(mockRequest, mockResponse);
      const result2 = controller.getCsrfToken(mockRequest, mockResponse);

      expect(result1.token).not.toBe(result2.token);
    });

    it("should set cookie with 24 hour expiry", () => {
      const mockRequest = createMockRequest("user-123");
      const mockResponse = createMockResponse();

      controller.getCsrfToken(mockRequest, mockResponse);

      expect(mockResponse.cookie).toHaveBeenCalledWith(
        "csrf-token",
        expect.any(String),
        expect.objectContaining({
          maxAge: 24 * 60 * 60 * 1000, // 24 hours
        })
      );
    });

    it("should throw error when user is not authenticated", () => {
      const mockRequest = createMockRequest(); // No user ID
      const mockResponse = createMockResponse();

      expect(() => controller.getCsrfToken(mockRequest, mockResponse)).toThrow(
        "User ID not available after authentication"
      );
    });

    it("should generate token bound to specific user session", () => {
      const mockRequest = createMockRequest("user-123");
      const mockResponse = createMockResponse();

      const result = controller.getCsrfToken(mockRequest, mockResponse);

      // Token should be valid for user-123
      expect(csrfService.validateToken(result.token, "user-123")).toBe(true);

      // Token should be invalid for different user
      expect(csrfService.validateToken(result.token, "user-456")).toBe(false);
    });

    it("should generate different tokens for different users", () => {
      const mockResponse = createMockResponse();

      const request1 = createMockRequest("user-A");
      const request2 = createMockRequest("user-B");

      const result1 = controller.getCsrfToken(request1, mockResponse);
      const result2 = controller.getCsrfToken(request2, mockResponse);

      expect(result1.token).not.toBe(result2.token);

      // Each token only valid for its user
      expect(csrfService.validateToken(result1.token, "user-A")).toBe(true);
      expect(csrfService.validateToken(result1.token, "user-B")).toBe(false);
      expect(csrfService.validateToken(result2.token, "user-B")).toBe(true);
      expect(csrfService.validateToken(result2.token, "user-A")).toBe(false);
    });
  });
});
