/**
 * Federation Auth Controller Tests
 *
 * Tests for federated authentication API endpoints.
 */

import { describe, it, expect, beforeEach, vi } from "vitest";
import { Test, TestingModule } from "@nestjs/testing";
import { FederationAuthController } from "./federation-auth.controller";
import { OIDCService } from "./oidc.service";
import { FederationAuditService } from "./audit.service";
import { AuthGuard } from "../auth/guards/auth.guard";
import type { AuthenticatedRequest } from "../common/types/user.types";
import type { FederatedIdentity } from "./types/oidc.types";
import {
  InitiateFederatedAuthDto,
  LinkFederatedIdentityDto,
  ValidateFederatedTokenDto,
} from "./dto/federated-auth.dto";

describe("FederationAuthController", () => {
  let controller: FederationAuthController;
  let oidcService: OIDCService;
  let auditService: FederationAuditService;

  const mockOIDCService = {
    generateAuthUrl: vi.fn(),
    linkFederatedIdentity: vi.fn(),
    getUserFederatedIdentities: vi.fn(),
    getFederatedIdentity: vi.fn(),
    revokeFederatedIdentity: vi.fn(),
    validateToken: vi.fn(),
  };

  const mockAuditService = {
    logFederatedAuthInitiation: vi.fn(),
    logFederatedIdentityLinked: vi.fn(),
    logFederatedIdentityRevoked: vi.fn(),
  };

  const mockUser = {
    id: "user-123",
    email: "user@example.com",
    workspaceId: "workspace-abc",
  };

  beforeEach(async () => {
    const module: TestingModule = await Test.createTestingModule({
      controllers: [FederationAuthController],
      providers: [
        { provide: OIDCService, useValue: mockOIDCService },
        { provide: FederationAuditService, useValue: mockAuditService },
      ],
    })
      .overrideGuard(AuthGuard)
      .useValue({ canActivate: () => true })
      .compile();

    controller = module.get<FederationAuthController>(FederationAuthController);
    oidcService = module.get<OIDCService>(OIDCService);
    auditService = module.get<FederationAuditService>(FederationAuditService);

    vi.clearAllMocks();
  });

  describe("POST /api/v1/federation/auth/initiate", () => {
    it("should initiate federated auth flow", () => {
      const dto: InitiateFederatedAuthDto = {
        remoteInstanceId: "remote-instance-123",
        redirectUrl: "http://localhost:3000/callback",
      };

      const mockAuthUrl = "https://auth.remote.com/authorize?client_id=abc&...";

      mockOIDCService.generateAuthUrl.mockReturnValue(mockAuthUrl);

      const req = { user: mockUser } as AuthenticatedRequest;
      const result = controller.initiateAuth(req, dto);

      expect(result).toEqual({
        authUrl: mockAuthUrl,
        state: dto.remoteInstanceId,
      });
      expect(mockOIDCService.generateAuthUrl).toHaveBeenCalledWith(
        dto.remoteInstanceId,
        dto.redirectUrl
      );
      expect(mockAuditService.logFederatedAuthInitiation).toHaveBeenCalledWith(
        mockUser.id,
        dto.remoteInstanceId
      );
    });

    it("should require authentication", () => {
      const dto: InitiateFederatedAuthDto = {
        remoteInstanceId: "remote-instance-123",
      };

      const req = { user: null } as unknown as AuthenticatedRequest;

      expect(() => controller.initiateAuth(req, dto)).toThrow();
    });
  });

  describe("POST /api/v1/federation/auth/link", () => {
    it("should link federated identity", async () => {
      const dto: LinkFederatedIdentityDto = {
        remoteInstanceId: "remote-instance-123",
        remoteUserId: "remote-user-456",
        oidcSubject: "oidc-sub-abc",
        email: "user@example.com",
        metadata: { displayName: "John Doe" },
      };

      const mockIdentity: FederatedIdentity = {
        id: "identity-uuid",
        localUserId: mockUser.id,
        remoteUserId: dto.remoteUserId,
        remoteInstanceId: dto.remoteInstanceId,
        oidcSubject: dto.oidcSubject,
        email: dto.email,
        metadata: dto.metadata ?? {},
        createdAt: new Date(),
        updatedAt: new Date(),
      };

      mockOIDCService.linkFederatedIdentity.mockResolvedValue(mockIdentity);

      const req = { user: mockUser } as AuthenticatedRequest;
      const result = await controller.linkIdentity(req, dto);

      expect(result).toEqual(mockIdentity);
      expect(mockOIDCService.linkFederatedIdentity).toHaveBeenCalledWith(
        mockUser.id,
        dto.remoteUserId,
        dto.remoteInstanceId,
        dto.oidcSubject,
        dto.email,
        dto.metadata
      );
      expect(mockAuditService.logFederatedIdentityLinked).toHaveBeenCalledWith(
        mockUser.id,
        dto.remoteInstanceId
      );
    });

    it("should require authentication", async () => {
      const dto: LinkFederatedIdentityDto = {
        remoteInstanceId: "remote-instance-123",
        remoteUserId: "remote-user-456",
        oidcSubject: "oidc-sub-abc",
        email: "user@example.com",
      };

      const req = { user: null } as unknown as AuthenticatedRequest;

      await expect(controller.linkIdentity(req, dto)).rejects.toThrow();
    });
  });

  describe("GET /api/v1/federation/auth/identities", () => {
    it("should return user's federated identities", async () => {
      const mockIdentities: FederatedIdentity[] = [
        {
          id: "identity-1",
          localUserId: mockUser.id,
          remoteUserId: "remote-1",
          remoteInstanceId: "instance-1",
          oidcSubject: "sub-1",
          email: mockUser.email,
          metadata: {},
          createdAt: new Date(),
          updatedAt: new Date(),
        },
        {
          id: "identity-2",
          localUserId: mockUser.id,
          remoteUserId: "remote-2",
          remoteInstanceId: "instance-2",
          oidcSubject: "sub-2",
          email: mockUser.email,
          metadata: {},
          createdAt: new Date(),
          updatedAt: new Date(),
        },
      ];

      mockOIDCService.getUserFederatedIdentities.mockResolvedValue(mockIdentities);

      const req = { user: mockUser } as AuthenticatedRequest;
      const result = await controller.getIdentities(req);

      expect(result).toEqual(mockIdentities);
      expect(mockOIDCService.getUserFederatedIdentities).toHaveBeenCalledWith(mockUser.id);
    });

    it("should require authentication", async () => {
      const req = { user: null } as unknown as AuthenticatedRequest;

      await expect(controller.getIdentities(req)).rejects.toThrow();
    });
  });

  describe("DELETE /api/v1/federation/auth/identities/:instanceId", () => {
    it("should revoke federated identity", async () => {
      const instanceId = "remote-instance-123";

      mockOIDCService.revokeFederatedIdentity.mockResolvedValue(undefined);

      const req = { user: mockUser } as AuthenticatedRequest;
      const result = await controller.revokeIdentity(req, instanceId);

      expect(result).toEqual({ success: true });
      expect(mockOIDCService.revokeFederatedIdentity).toHaveBeenCalledWith(mockUser.id, instanceId);
      expect(mockAuditService.logFederatedIdentityRevoked).toHaveBeenCalledWith(
        mockUser.id,
        instanceId
      );
    });

    it("should require authentication", async () => {
      const req = { user: null } as unknown as AuthenticatedRequest;

      await expect(controller.revokeIdentity(req, "instance-123")).rejects.toThrow();
    });
  });

  describe("POST /api/v1/federation/auth/validate", () => {
    it("should validate federated token", async () => {
      const dto: ValidateFederatedTokenDto = {
        token: "valid-token",
        instanceId: "remote-instance-123",
      };

      const mockValidation = {
        valid: true,
        userId: "user-subject-123",
        instanceId: dto.instanceId,
        email: "user@example.com",
        subject: "user-subject-123",
      };

      mockOIDCService.validateToken.mockResolvedValue(mockValidation);

      const result = await controller.validateToken(dto);

      expect(result).toEqual(mockValidation);
      expect(mockOIDCService.validateToken).toHaveBeenCalledWith(dto.token, dto.instanceId);
    });

    it("should return invalid for expired token", async () => {
      const dto: ValidateFederatedTokenDto = {
        token: "expired-token",
        instanceId: "remote-instance-123",
      };

      const mockValidation = {
        valid: false,
        error: "Token has expired",
      };

      mockOIDCService.validateToken.mockResolvedValue(mockValidation);

      const result = await controller.validateToken(dto);

      expect(result.valid).toBe(false);
      expect(result.error).toBeDefined();
    });
  });
});
