import { describe, it, expect, vi, beforeEach } from 'vitest';
import { DataSource, Repository, ObjectLiteral } from 'typeorm';
import { GenericCrudService, UserTrackingOptions, RelationFieldOptions } from '../../src/services/generic-crud.service';

// 测试实体类
class TestEntity implements ObjectLiteral {
  id!: number;
  name!: string;
  createdBy?: string;
  updatedBy?: string;
  userId?: string;
}

// 测试关联实体类
class RelatedEntity implements ObjectLiteral {
  id!: number;
  name!: string;
}

describe('GenericCrudService', () => {
  let mockDataSource: DataSource;
  let mockRepository: Repository<TestEntity>;
  let crudService: GenericCrudService<TestEntity>;

  beforeEach(() => {
    // 创建模拟数据源和仓库
    mockRepository = {
      createQueryBuilder: vi.fn().mockReturnValue({
        leftJoinAndSelect: vi.fn().mockReturnThis(),
        andWhere: vi.fn().mockReturnThis(),
        orderBy: vi.fn().mockReturnThis(),
        skip: vi.fn().mockReturnThis(),
        take: vi.fn().mockReturnThis(),
        getManyAndCount: vi.fn().mockResolvedValue([[], 0])
      }),
      findOne: vi.fn().mockResolvedValue(null),
      create: vi.fn().mockImplementation((data) => ({ ...data, id: 1 })),
      save: vi.fn().mockImplementation((entity) => Promise.resolve(entity)),
      update: vi.fn().mockResolvedValue({ affected: 1 }),
      delete: vi.fn().mockResolvedValue({ affected: 1 })
    } as any;

    mockDataSource = {
      getRepository: vi.fn().mockReturnValue(mockRepository)
    } as any;

    crudService = new GenericCrudService(mockDataSource, TestEntity);
  });

  describe('构造函数', () => {
    it('应该正确初始化仓库', () => {
      expect(mockDataSource.getRepository).toHaveBeenCalledWith(TestEntity);
    });

    it('应该支持用户跟踪选项', () => {
      const userTrackingOptions: UserTrackingOptions = {
        createdByField: 'creator',
        updatedByField: 'updater',
        userIdField: 'ownerId'
      };

      const serviceWithTracking = new GenericCrudService(
        mockDataSource,
        TestEntity,
        { userTracking: userTrackingOptions }
      );

      expect(serviceWithTracking).toBeDefined();
    });

    it('应该支持关联字段选项', () => {
      const relationFields: RelationFieldOptions = {
        relatedIds: {
          relationName: 'relatedEntities',
          targetEntity: RelatedEntity
        }
      };

      const serviceWithRelations = new GenericCrudService(
        mockDataSource,
        TestEntity,
        { relationFields }
      );

      expect(serviceWithRelations).toBeDefined();
    });
  });

  describe('getList 方法', () => {
    it('应该调用查询构建器获取列表', async () => {
      const mockQueryBuilder = {
        leftJoinAndSelect: vi.fn().mockReturnThis(),
        andWhere: vi.fn().mockReturnThis(),
        orderBy: vi.fn().mockReturnThis(),
        skip: vi.fn().mockReturnThis(),
        take: vi.fn().mockReturnThis(),
        getManyAndCount: vi.fn().mockResolvedValue([[], 0])
      };

      vi.mocked(mockRepository.createQueryBuilder).mockReturnValue(mockQueryBuilder as any);

      const [data, total] = await crudService.getList(1, 10);

      expect(mockRepository.createQueryBuilder).toHaveBeenCalledWith('entity');
      expect(mockQueryBuilder.skip).toHaveBeenCalledWith(0);
      expect(mockQueryBuilder.take).toHaveBeenCalledWith(10);
      expect(data).toEqual([]);
      expect(total).toBe(0);
    });

    it('应该支持关键词搜索', async () => {
      const mockQueryBuilder = {
        leftJoinAndSelect: vi.fn().mockReturnThis(),
        andWhere: vi.fn().mockReturnThis(),
        orderBy: vi.fn().mockReturnThis(),
        skip: vi.fn().mockReturnThis(),
        take: vi.fn().mockReturnThis(),
        getManyAndCount: vi.fn().mockResolvedValue([[], 0])
      };

      vi.mocked(mockRepository.createQueryBuilder).mockReturnValue(mockQueryBuilder as any);

      await crudService.getList(1, 10, 'test', ['name']);

      expect(mockQueryBuilder.andWhere).toHaveBeenCalled();
    });
  });

  describe('getById 方法', () => {
    it('应该根据ID获取实体', async () => {
      const testEntity = { id: 1, name: 'Test' };
      vi.mocked(mockRepository.findOne).mockResolvedValue(testEntity as any);

      const result = await crudService.getById(1);

      expect(mockRepository.findOne).toHaveBeenCalledWith({
        where: { id: 1 },
        relations: []
      });
      expect(result).toEqual(testEntity);
    });

    it('应该支持关联关系', async () => {
      await crudService.getById(1, ['relatedEntity']);

      expect(mockRepository.findOne).toHaveBeenCalledWith({
        where: { id: 1 },
        relations: ['relatedEntity']
      });
    });
  });

  describe('create 方法', () => {
    it('应该创建新实体', async () => {
      const createData = { name: 'New Entity' };
      const savedEntity = { id: 1, ...createData };

      vi.mocked(mockRepository.create).mockReturnValue(savedEntity as any);
      vi.mocked(mockRepository.save).mockResolvedValue(savedEntity as any);

      const result = await crudService.create(createData);

      expect(mockRepository.create).toHaveBeenCalledWith(createData);
      expect(mockRepository.save).toHaveBeenCalledWith(savedEntity);
      expect(result).toEqual(savedEntity);
    });

    it('应该支持用户跟踪', async () => {
      const userTrackingOptions: UserTrackingOptions = {
        createdByField: 'createdBy',
        updatedByField: 'updatedBy'
      };

      const serviceWithTracking = new GenericCrudService(
        mockDataSource,
        TestEntity,
        { userTracking: userTrackingOptions }
      );

      const createData = { name: 'New Entity' };
      const savedEntity = { id: 1, ...createData, createdBy: 'user123', updatedBy: 'user123' };

      vi.mocked(mockRepository.create).mockReturnValue(savedEntity as any);
      vi.mocked(mockRepository.save).mockResolvedValue(savedEntity as any);

      await serviceWithTracking.create(createData, 'user123');

      expect(mockRepository.create).toHaveBeenCalledWith({
        ...createData,
        createdBy: 'user123',
        updatedBy: 'user123',
        userId: 'user123'
      });
    });
  });

  describe('update 方法', () => {
    it('应该更新现有实体', async () => {
      const updateData = { name: 'Updated Entity' };
      const existingEntity = { id: 1, name: 'Original Entity' };
      const updatedEntity = { ...existingEntity, ...updateData };

      vi.mocked(mockRepository.findOne).mockResolvedValue(existingEntity as any);
      vi.mocked(mockRepository.save).mockResolvedValue(updatedEntity as any);

      const result = await crudService.update(1, updateData);

      expect(mockRepository.update).toHaveBeenCalledWith(1, updateData);
      expect(mockRepository.findOne).toHaveBeenCalledWith({
        where: { id: 1 },
        relations: []
      });
      expect(result).toEqual(updatedEntity);
    });

    it('应该返回 null 当实体不存在时', async () => {
      vi.mocked(mockRepository.findOne).mockResolvedValue(null);

      const result = await crudService.update(999, { name: 'Updated' });

      expect(result).toBeNull();
    });
  });

  describe('delete 方法', () => {
    it('应该删除实体', async () => {
      vi.mocked(mockRepository.delete).mockResolvedValue({ affected: 1 } as any);

      const result = await crudService.delete(1);

      expect(mockRepository.delete).toHaveBeenCalledWith(1);
      expect(result).toBe(true);
    });

    it('应该返回 false 当实体不存在时', async () => {
      vi.mocked(mockRepository.delete).mockResolvedValue({ affected: 0 } as any);

      const result = await crudService.delete(999);

      expect(result).toBe(false);
    });
  });

  describe('createQueryBuilder 方法', () => {
    it('应该返回查询构建器', () => {
      const mockQueryBuilder = {} as any;
      vi.mocked(mockRepository.createQueryBuilder).mockReturnValue(mockQueryBuilder);

      const result = crudService.createQueryBuilder('test');

      expect(mockRepository.createQueryBuilder).toHaveBeenCalledWith('test');
      expect(result).toBe(mockQueryBuilder);
    });

    it('应该使用默认别名', () => {
      const mockQueryBuilder = {} as any;
      vi.mocked(mockRepository.createQueryBuilder).mockReturnValue(mockQueryBuilder);

      crudService.createQueryBuilder();

      expect(mockRepository.createQueryBuilder).toHaveBeenCalledWith('entity');
    });
  });
});