/**
 * Identity Resolution Service Tests
 *
 * Tests for resolving identities between local and remote instances.
 */

import { describe, it, expect, beforeEach, afterEach, vi } from "vitest";
import { Test, TestingModule } from "@nestjs/testing";
import { IdentityResolutionService } from "./identity-resolution.service";
import { IdentityLinkingService } from "./identity-linking.service";
import type { FederatedIdentity } from "./types/oidc.types";

describe("IdentityResolutionService", () => {
  let service: IdentityResolutionService;
  let identityLinkingService: IdentityLinkingService;

  const mockIdentity: FederatedIdentity = {
    id: "identity-id",
    localUserId: "local-user-id",
    remoteUserId: "remote-user-id",
    remoteInstanceId: "remote-instance-id",
    oidcSubject: "oidc-subject",
    email: "user@example.com",
    metadata: {},
    createdAt: new Date(),
    updatedAt: new Date(),
  };

  beforeEach(async () => {
    const mockIdentityLinkingService = {
      resolveLocalIdentity: vi.fn(),
      resolveRemoteIdentity: vi.fn(),
    };

    const module: TestingModule = await Test.createTestingModule({
      providers: [
        IdentityResolutionService,
        { provide: IdentityLinkingService, useValue: mockIdentityLinkingService },
      ],
    }).compile();

    service = module.get<IdentityResolutionService>(IdentityResolutionService);
    identityLinkingService = module.get(IdentityLinkingService);
  });

  afterEach(() => {
    vi.clearAllMocks();
  });

  describe("resolveIdentity", () => {
    it("should resolve remote identity to local user", async () => {
      identityLinkingService.resolveLocalIdentity.mockResolvedValue(mockIdentity);

      const result = await service.resolveIdentity("remote-instance-id", "remote-user-id");

      expect(result.found).toBe(true);
      expect(result.localUserId).toBe("local-user-id");
      expect(result.remoteUserId).toBe("remote-user-id");
      expect(result.email).toBe("user@example.com");
      expect(identityLinkingService.resolveLocalIdentity).toHaveBeenCalledWith(
        "remote-instance-id",
        "remote-user-id"
      );
    });

    it("should return not found when mapping does not exist", async () => {
      identityLinkingService.resolveLocalIdentity.mockResolvedValue(null);

      const result = await service.resolveIdentity("remote-instance-id", "unknown-user-id");

      expect(result.found).toBe(false);
      expect(result.localUserId).toBeUndefined();
      expect(result.remoteUserId).toBe("unknown-user-id");
    });
  });

  describe("reverseResolveIdentity", () => {
    it("should resolve local user to remote identity", async () => {
      identityLinkingService.resolveRemoteIdentity.mockResolvedValue(mockIdentity);

      const result = await service.reverseResolveIdentity("local-user-id", "remote-instance-id");

      expect(result.found).toBe(true);
      expect(result.remoteUserId).toBe("remote-user-id");
      expect(result.localUserId).toBe("local-user-id");
      expect(identityLinkingService.resolveRemoteIdentity).toHaveBeenCalledWith(
        "local-user-id",
        "remote-instance-id"
      );
    });

    it("should return not found when mapping does not exist", async () => {
      identityLinkingService.resolveRemoteIdentity.mockResolvedValue(null);

      const result = await service.reverseResolveIdentity("unknown-user-id", "remote-instance-id");

      expect(result.found).toBe(false);
      expect(result.remoteUserId).toBeUndefined();
      expect(result.localUserId).toBe("unknown-user-id");
    });
  });

  describe("bulkResolveIdentities", () => {
    it("should resolve multiple remote users to local users", async () => {
      const mockIdentity2: FederatedIdentity = {
        ...mockIdentity,
        id: "identity-id-2",
        localUserId: "local-user-id-2",
        remoteUserId: "remote-user-id-2",
      };

      identityLinkingService.resolveLocalIdentity
        .mockResolvedValueOnce(mockIdentity)
        .mockResolvedValueOnce(mockIdentity2)
        .mockResolvedValueOnce(null);

      const result = await service.bulkResolveIdentities("remote-instance-id", [
        "remote-user-id",
        "remote-user-id-2",
        "unknown-user-id",
      ]);

      expect(result.mappings["remote-user-id"]).toBe("local-user-id");
      expect(result.mappings["remote-user-id-2"]).toBe("local-user-id-2");
      expect(result.notFound).toEqual(["unknown-user-id"]);
      expect(identityLinkingService.resolveLocalIdentity).toHaveBeenCalledTimes(3);
    });

    it("should handle empty array", async () => {
      const result = await service.bulkResolveIdentities("remote-instance-id", []);

      expect(result.mappings).toEqual({});
      expect(result.notFound).toEqual([]);
      expect(identityLinkingService.resolveLocalIdentity).not.toHaveBeenCalled();
    });

    it("should handle all not found", async () => {
      identityLinkingService.resolveLocalIdentity
        .mockResolvedValueOnce(null)
        .mockResolvedValueOnce(null);

      const result = await service.bulkResolveIdentities("remote-instance-id", [
        "unknown-1",
        "unknown-2",
      ]);

      expect(result.mappings).toEqual({});
      expect(result.notFound).toEqual(["unknown-1", "unknown-2"]);
    });
  });
});
