/**
 * OrganizationsService Unit Tests
 * 
 * 测试组织管理服务 (v2.1 - 组织级权限隔离)
 * 
 * 测试覆盖:
 * - 创建组织
 * - 更新组织信息
 * - 删除组织
 * - 组织区域关联
 * - 组织统计信息
 * - 业务规则验证
 * 
 * 基于文档: docs/modules/organization/09-test-scenarios.md
 */

import { Test, TestingModule } from '@nestjs/testing';
import { ConflictException, NotFoundException, BadRequestException } from '@nestjs/common';
import { OrganizationsService } from '@/modules/organization/organizations/organizations.service';
import { PrismaService } from '@/core/database/prisma/prisma.service';
import { OrganizationStatus } from '@/modules/organization/organizations/dto';

describe('OrganizationsService', () => {
  let service: OrganizationsService;
  let prisma: PrismaService;

  // Mock data with all required fields
  const mockOrganization = {
    id: 'org-test-001',
    code: 'FF-CN',
    name: 'FF China',
    displayName: 'Faraday Future China',
    nameEn: 'FF China',
    nameZh: '法拉第未来中国',
    legalName: 'Flying Fox China Co., Ltd.',
    legalRepresentative: 'John Doe',
    registrationNumber: '91110000MA001234XX',
    taxId: '91110000MA001234XX',
    address: '北京市朝阳区',
    phone: '+86-010-12345678',
    email: 'info@ff.com',
    website: 'https://www.ff.com',
    primaryRegionId: null,
    settings: {},
    financialConfig: {},
    complianceConfig: {},
    status: 'ACTIVE',
    isActive: true,
    order: 0,
    metadata: {},
    establishedAt: null,
    createdAt: new Date(),
    updatedAt: new Date(),
    deletedAt: null,
  };

  const mockRegion = {
    id: 'region-cn',
    code: 'CN',
    name: 'China',
    nameEn: 'China',
    nameZh: '中国',
    isActive: true,
    order: 0,
    metadata: {},
    timezone: 'Asia/Shanghai',
    currency: 'CNY',
    locale: 'zh-CN',
    createdAt: new Date(),
    updatedAt: new Date(),
    deletedAt: null,
  };

  beforeEach(async () => {
    const module: TestingModule = await Test.createTestingModule({
      providers: [
        OrganizationsService,
        {
          provide: PrismaService,
          useValue: {
            organization: {
              create: jest.fn(),
              findUnique: jest.fn(),
              findFirst: jest.fn(),
              findMany: jest.fn(),
              update: jest.fn(),
              delete: jest.fn(),
              count: jest.fn(),
            },
            organizationRegion: {
              create: jest.fn(),
              createMany: jest.fn(),
              findUnique: jest.fn(), // 新增：用于 removeRegion
              findFirst: jest.fn(),
              findMany: jest.fn(),
              delete: jest.fn(),
              deleteMany: jest.fn(),
              count: jest.fn(),
            },
            region: {
              findUnique: jest.fn(),
              findMany: jest.fn(),
            },
            department: {
              count: jest.fn(),
              create: jest.fn(),
              updateMany: jest.fn(),
            },
            userDepartment: {
              count: jest.fn(),
              findMany: jest.fn(),
            },
            $transaction: jest.fn(),
          },
        },
      ],
    }).compile();

    service = module.get<OrganizationsService>(OrganizationsService);
    prisma = module.get<PrismaService>(PrismaService);
  });

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

  describe('create', () => {
    it('[测试场景 2.1.1] 应该成功创建组织并自动创建根部门', async () => {
      const dto = {
        code: 'FF-CN',
        name: 'FF China',
        legalName: 'Flying Fox China Co., Ltd.',
        taxId: '91110000MA001234XX',
        address: '北京市朝阳区',
      };

      const mockRootDepartment = {
        id: 'dept-root-001',
        organizationId: 'org-test-001',
        name: 'FF China',
        code: 'FF-CN',
        parentId: null,
        headId: null,
        description: 'FF China 顶级部门（自动创建）',
        order: 0,
        metadata: {},
        createdAt: new Date(),
        updatedAt: new Date(),
        deletedAt: null,
      };

      jest.spyOn(prisma.organization, 'findFirst').mockResolvedValue(null);
      jest.spyOn(prisma, '$transaction').mockImplementation(async (callback: any) => {
        return callback({
          organization: {
            create: jest.fn().mockResolvedValue(mockOrganization),
          },
          department: {
            create: jest.fn().mockResolvedValue(mockRootDepartment),
          },
        });
      });
      jest.spyOn(prisma.organization, 'findUnique').mockResolvedValue({
        ...mockOrganization,
        departments: [mockRootDepartment],
      } as any);

      const result = await service.create(dto as any);

      expect(result).toBeDefined();
      expect(result!.id).toBe('org-test-001');
      expect(result!.name).toBe('FF China');
      expect(result!.code).toBe('FF-CN');
      expect(result!.legalName).toBe('Flying Fox China Co., Ltd.');
      expect(result!.status).toBe('ACTIVE');
      expect((result as any).departments).toBeDefined();
      expect((result as any).departments.length).toBeGreaterThan(0);
      expect((result as any).departments[0].parentId).toBeNull(); // 根部门
    });

    it('[测试场景 2.1.2] 组织名称重复应抛出异常', async () => {
      const dto = {
        code: 'FF-CN',
        name: 'FF China',
        legalName: 'Flying Fox China Co., Ltd.',
      };

      jest.spyOn(prisma.organization, 'findFirst').mockResolvedValue({
        ...mockOrganization,
        name: dto.name,
        code: 'DIFFERENT-CODE', // 不同的 code，但相同的 name
      } as any);

      await expect(service.create(dto as any)).rejects.toThrow(ConflictException);
      await expect(service.create(dto as any)).rejects.toThrow('name');

      expect(prisma.organization.create).not.toHaveBeenCalled();
    });

    it('[测试场景 2.1.3] 税号重复应抛出异常', async () => {
      const dto = {
        code: 'FF-CN2',
        name: 'FF China 2',
        taxId: '91110000MA001234XX',
      };

      jest.spyOn(prisma.organization, 'findFirst').mockResolvedValue({
        ...mockOrganization,
        code: 'DIFFERENT-CODE',
        name: 'Different Name',
        taxId: dto.taxId, // 相同的 taxId
      } as any);

      await expect(service.create(dto as any)).rejects.toThrow(ConflictException);
      await expect(service.create(dto as any)).rejects.toThrow('Tax ID');

      expect(prisma.organization.create).not.toHaveBeenCalled();
    });

    it('应该正确处理可选字段', async () => {
      const dto = {
        code: 'FF-CN',
        name: 'FF China',
      };

      const mockResult = {
        ...mockOrganization,
        legalName: null,
        taxId: null,
        address: null,
      };

      jest.spyOn(prisma.organization, 'findFirst').mockResolvedValue(null);
      jest.spyOn(prisma, '$transaction').mockImplementation(async (callback: any) => {
        return callback({
          organization: {
            create: jest.fn().mockResolvedValue(mockResult),
          },
          department: {
            create: jest.fn().mockResolvedValue({}),
          },
        });
      });
      jest.spyOn(prisma.organization, 'findUnique').mockResolvedValue({
        ...mockResult,
        departments: [],
      } as any);

      const result = await service.create(dto as any);

      expect(result).toBeDefined();
      expect(result!.legalName).toBeNull();
      expect(result!.taxId).toBeNull();
      expect(result!.address).toBeNull();
    });
  });

  describe('update', () => {
    it('[测试场景 2.2.1] 应该成功更新组织信息', async () => {
      const orgId = 'org-test-001';
      const updateDto = {
        name: 'New Name',
        address: '新地址',
      };

      const updatedOrg = {
        ...mockOrganization,
        ...updateDto,
        updatedAt: new Date(Date.now() + 1000),
      };

      jest.spyOn(prisma.organization, 'findFirst').mockResolvedValue(mockOrganization as any);
      jest.spyOn(prisma.organization, 'update').mockResolvedValue(updatedOrg as any);

      const result = await service.update(orgId, updateDto as any);

      expect(result.name).toBe('New Name');
      expect(result.address).toBe('新地址');
      expect(result.updatedAt.getTime()).toBeGreaterThan(mockOrganization.updatedAt.getTime());
    });

    it('[测试场景 2.2.2] 更新不存在的组织应抛出异常', async () => {
      const orgId = 'non-existent-id';
      const updateDto = {
        name: 'New Name',
      };

      jest.spyOn(prisma.organization, 'findFirst').mockResolvedValue(null);

      await expect(service.update(orgId, updateDto as any)).rejects.toThrow(NotFoundException);
      await expect(service.update(orgId, updateDto as any)).rejects.toThrow("Organization 'non-existent-id' not found");

      expect(prisma.organization.update).not.toHaveBeenCalled();
    });
  });

  describe('remove', () => {
    it('[测试场景 2.3.1] 应该成功软删除组织', async () => {
      const orgId = 'org-test-001';

      jest.spyOn(prisma.organization, 'findFirst').mockResolvedValue(mockOrganization as any);
      jest.spyOn(prisma.userDepartment, 'count').mockResolvedValue(0); // 无用户
      jest.spyOn(prisma, '$transaction').mockImplementation(async (callback: any) => {
        return callback({
          department: {
            updateMany: jest.fn().mockResolvedValue({ count: 2 }),
          },
          organization: {
            update: jest.fn().mockResolvedValue({
              ...mockOrganization,
              deletedAt: new Date(),
              isActive: false,
            }),
          },
        });
      });

      const result = await service.remove(orgId);

      expect(result).toEqual({ message: 'Organization deleted successfully' });
    });

    it('[测试场景 2.3.2] 有部门时应该一起删除（v2.1.17 变更）', async () => {
      const orgId = 'org-test-001';

      jest.spyOn(prisma.organization, 'findFirst').mockResolvedValue(mockOrganization as any);
      jest.spyOn(prisma.userDepartment, 'count').mockResolvedValue(0); // 无用户
      jest.spyOn(prisma, '$transaction').mockImplementation(async (callback: any) => {
        return callback({
          department: {
            updateMany: jest.fn().mockResolvedValue({ count: 3 }), // 删除 3 个部门
          },
          organization: {
            update: jest.fn().mockResolvedValue({
              ...mockOrganization,
              deletedAt: new Date(),
              isActive: false,
            }),
          },
        });
      });

      const result = await service.remove(orgId);

      // v2.1.17: 删除组织时自动删除所有部门（包括根部门）
      expect(result).toEqual({ message: 'Organization deleted successfully' });
    });

    it('[测试场景 2.3.3] 有用户时无法删除组织', async () => {
      const orgId = 'org-test-001';

      jest.spyOn(prisma.organization, 'findFirst').mockResolvedValue(mockOrganization as any);
      jest.spyOn(prisma.department, 'count').mockResolvedValue(0); // 无部门
      jest.spyOn(prisma.userDepartment, 'count').mockResolvedValue(5); // 有 5 个用户

      await expect(service.remove(orgId)).rejects.toThrow(BadRequestException);
      await expect(service.remove(orgId)).rejects.toThrow('active users exist');

      expect(prisma.organization.update).not.toHaveBeenCalled();
    });

    it('删除不存在的组织应抛出异常', async () => {
      const orgId = 'non-existent-id';

      jest.spyOn(prisma.organization, 'findFirst').mockResolvedValue(null);

      await expect(service.remove(orgId)).rejects.toThrow(NotFoundException);

      expect(prisma.organization.update).not.toHaveBeenCalled();
    });
  });

  describe('addRegions', () => {
    it('[测试场景 2.4.1] 应该成功添加区域到组织', async () => {
      const orgId = 'org-test-001';
      const regionId = 'region-cn';
      const dto = {
        regionIds: [regionId],
        primaryRegionId: regionId,
      };

      const mockOrganizationWithRegions = {
        ...mockOrganization,
        primaryRegionId: regionId,
        organizationRegions: [
          {
            id: 'org-region-001',
            organizationId: orgId,
            regionId,
            isDefault: true,
            createdAt: new Date(),
            region: mockRegion,
          },
        ],
      };

      jest.spyOn(prisma.organization, 'findFirst').mockResolvedValue(mockOrganization as any);
      jest.spyOn(prisma.region, 'findMany').mockResolvedValue([mockRegion] as any);
      jest.spyOn(prisma.organizationRegion, 'deleteMany').mockResolvedValue({ count: 0 } as any);
      jest.spyOn(prisma.organizationRegion, 'createMany').mockResolvedValue({ count: 1 } as any);
      jest.spyOn(prisma.organization, 'update').mockResolvedValue(mockOrganization as any);
      // Mock findOne 返回完整对象（第二次调用）
      jest.spyOn(prisma.organization, 'findFirst').mockResolvedValueOnce(mockOrganization as any).mockResolvedValueOnce(mockOrganizationWithRegions as any);

      const result = await service.addRegions(orgId, dto as any);

      expect(result.organizationRegions).toHaveLength(1);
      expect(result.organizationRegions[0].organizationId).toBe(orgId);
      expect(result.organizationRegions[0].regionId).toBe(regionId);
      expect(result.organizationRegions[0].isDefault).toBe(true);
    });

    it('[测试场景 2.4.3] 部分区域不存在应抛出异常', async () => {
      const orgId = 'org-test-001';
      const regionId = 'region-cn';
      const missingRegionId = 'region-missing';
      const dto = {
        regionIds: [regionId, missingRegionId],
      };

      jest.spyOn(prisma.organization, 'findFirst').mockResolvedValue(mockOrganization as any);
      jest.spyOn(prisma.region, 'findMany').mockResolvedValue([mockRegion] as any); // 只返回一个区域

      await expect(service.addRegions(orgId, dto as any)).rejects.toThrow(NotFoundException);
      await expect(service.addRegions(orgId, dto as any)).rejects.toThrow('Regions not found');

      expect(prisma.organizationRegion.createMany).not.toHaveBeenCalled();
    });
  });

  describe('removeRegion', () => {
    const mockOrganization = {
      id: 'org-test-001',
      code: 'ORG001',
      name: 'Test Org',
      primaryRegionId: 'region-cn',
      organizationRegions: [],
      deletedAt: null,
    };

    const mockOrgRegion = {
      organizationId: 'org-test-001',
      regionId: 'region-us',
      isDefault: false,
    };

    it('[测试场景 2.4.2] 应该成功移除非主要区域', async () => {
      jest.spyOn(prisma.organization, 'findFirst').mockResolvedValue(mockOrganization as any);
      jest.spyOn(prisma.organizationRegion, 'findUnique').mockResolvedValue(mockOrgRegion as any);
      jest.spyOn(prisma.organizationRegion, 'delete').mockResolvedValue(mockOrgRegion as any);

      const result = await service.removeRegion('org-test-001', 'region-us');

      expect(result.message).toBe('Region removed successfully');
      expect(prisma.organizationRegion.delete).toHaveBeenCalledWith({
        where: {
          organizationId_regionId: {
            organizationId: 'org-test-001',
            regionId: 'region-us',
          },
        },
      });
    });

    it('应该防止移除主要区域', async () => {
      jest.spyOn(prisma.organization, 'findFirst').mockResolvedValue(mockOrganization as any);
      jest.spyOn(prisma.organizationRegion, 'findUnique').mockResolvedValue({
        ...mockOrgRegion,
        regionId: 'region-cn',
        isDefault: true,
      } as any);

      await expect(
        service.removeRegion('org-test-001', 'region-cn')
      ).rejects.toThrow(BadRequestException);
      await expect(
        service.removeRegion('org-test-001', 'region-cn')
      ).rejects.toThrow('Cannot remove primary region');

      expect(prisma.organizationRegion.delete).not.toHaveBeenCalled();
    });

    it('应该在区域关联不存在时抛出异常', async () => {
      jest.spyOn(prisma.organization, 'findFirst').mockResolvedValue(mockOrganization as any);
      jest.spyOn(prisma.organizationRegion, 'findUnique').mockResolvedValue(null);

      await expect(
        service.removeRegion('org-test-001', 'region-non-existent')
      ).rejects.toThrow(NotFoundException);
      await expect(
        service.removeRegion('org-test-001', 'region-non-existent')
      ).rejects.toThrow('is not associated with');

      expect(prisma.organizationRegion.delete).not.toHaveBeenCalled();
    });
  });

  describe('getStats', () => {
    it('[测试场景 2.5.1] 应该获取组织统计信息', async () => {
      const orgId = 'org-test-001';

      jest.spyOn(prisma.organization, 'findFirst').mockResolvedValue(mockOrganization as any);
      jest.spyOn(prisma.department, 'count').mockResolvedValue(5);
      jest.spyOn(prisma.userDepartment, 'findMany').mockResolvedValue([
        {
          userId: 'user-1',
          user: { status: 'ACTIVE' },
        },
        {
          userId: 'user-2',
          user: { status: 'ACTIVE' },
        },
        {
          userId: 'user-3',
          user: { status: 'INACTIVE' },
        },
      ] as any);
      jest.spyOn(prisma.organizationRegion, 'count').mockResolvedValue(2);

      const result = await service.getStats(orgId);

      expect(result).toEqual({
        totalDepartments: 5,
        totalUsers: 3,
        activeUsers: 2,
        totalRegions: 2,
      });
    });

    it('获取不存在组织的统计应抛出异常', async () => {
      const orgId = 'non-existent-id';

      jest.spyOn(prisma.organization, 'findFirst').mockResolvedValue(null);

      await expect(service.getStats(orgId)).rejects.toThrow(NotFoundException);
    });
  });

  describe('findOne', () => {
    it('应该成功查找组织', async () => {
      const orgId = 'org-test-001';

      jest.spyOn(prisma.organization, 'findFirst').mockResolvedValue(mockOrganization as any);

      const result = await service.findOne(orgId);

      expect(result).toEqual(mockOrganization);
      expect(prisma.organization.findFirst).toHaveBeenCalledWith(
        expect.objectContaining({
          where: {
            id: orgId,
            deletedAt: null,
          },
        })
      );
    });

    it('查找不存在的组织应抛出异常', async () => {
      const orgId = 'non-existent-id';

      jest.spyOn(prisma.organization, 'findFirst').mockResolvedValue(null);

      await expect(service.findOne(orgId)).rejects.toThrow(NotFoundException);
      await expect(service.findOne(orgId)).rejects.toThrow("Organization 'non-existent-id' not found");
    });
  });

  describe('findAll', () => {
    it('应该返回所有活动组织（分页格式）', async () => {
      const mockOrganizations = [
        mockOrganization,
        { ...mockOrganization, id: 'org-test-002', name: 'FF USA', code: 'FF-US' },
      ];

      jest.spyOn(prisma.organization, 'findMany').mockResolvedValue(mockOrganizations as any);
      jest.spyOn(prisma.organization, 'count').mockResolvedValue(2);

      const result = await service.findAll({});

      // findAll 返回分页对象
      expect(result).toHaveProperty('items');
      expect(result).toHaveProperty('total');
      expect(result).toHaveProperty('page');
      expect(result).toHaveProperty('pageSize');
      expect(result).toHaveProperty('totalPages');
      expect(result.items).toHaveLength(2);
      expect(result.total).toBe(2);
      expect(result.items).toEqual(mockOrganizations);
    });

    it('应该支持过滤条件', async () => {
      const mockOrganizations = [mockOrganization];

      jest.spyOn(prisma.organization, 'findMany').mockResolvedValue(mockOrganizations as any);
      jest.spyOn(prisma.organization, 'count').mockResolvedValue(1);

      const result = await service.findAll({ status: OrganizationStatus.ACTIVE });

      expect(result.items).toHaveLength(1);
      expect(result.total).toBe(1);
      expect(prisma.organization.findMany).toHaveBeenCalledWith(
        expect.objectContaining({
          where: expect.objectContaining({
            deletedAt: null,
            status: OrganizationStatus.ACTIVE,
          }),
        })
      );
    });
  });

  describe('业务规则验证', () => {
    it('应该处理并发创建冲突', async () => {
      const dto = {
        code: 'FF-CN',
        name: 'FF China',
      };

      // 模拟并发场景：第一次检查通过，但创建时已存在
      jest.spyOn(prisma.organization, 'findFirst').mockResolvedValue(null);
      jest.spyOn(prisma.organization, 'create').mockRejectedValue(
        new Error('Unique constraint failed')
      );

      await expect(service.create(dto as any)).rejects.toThrow();
    });
  });
});
