import { describe, it, expect, vi, beforeEach } from 'vitest';
import { PrismaUserRepository } from '../repositories/prisma-user.repository';

// Mock domain value objects
vi.mock('../../domain/value-objects/phone.vo', () => ({
  Phone: {
    create: vi.fn((value: string) => ({
      isOk: true,
      unwrap: () => ({ value: value.startsWith('+84') ? value : '+84' + value.slice(1) }),
    })),
  },
}));

vi.mock('../../domain/value-objects/email.vo', () => ({
  Email: {
    create: vi.fn((value: string) => ({
      isOk: true,
      unwrap: () => ({ value }),
    })),
  },
}));

vi.mock('../../domain/value-objects/hashed-password.vo', () => ({
  HashedPassword: {
    fromHash: vi.fn((hash: string) => ({ value: hash })),
  },
}));

vi.mock('../../domain/entities/user.entity', () => ({
  UserEntity: class MockUserEntity {
    constructor(
      public id: string,
      public props: any,
      public createdAt: Date,
      public updatedAt: Date,
    ) {
      Object.assign(this, props);
    }
  },
}));

describe('PrismaUserRepository', () => {
  let repository: PrismaUserRepository;
  let mockPrisma: {
    user: {
      findUnique: ReturnType<typeof vi.fn>;
      findFirst: ReturnType<typeof vi.fn>;
      create: ReturnType<typeof vi.fn>;
      update: ReturnType<typeof vi.fn>;
    };
    fieldEncryption: {
      computeHash: ReturnType<typeof vi.fn>;
    };
  };

  const mockPrismaUser = {
    id: 'user-1',
    phone: '+84912345678',
    email: 'test@example.com',
    passwordHash: '$2b$10$hashedpassword',
    fullName: 'Nguyen Van A',
    avatarUrl: null,
    role: 'BUYER',
    kycStatus: 'NONE',
    kycData: null,
    isActive: true,
    createdAt: new Date('2024-01-01'),
    updatedAt: new Date('2024-01-02'),
  };

  beforeEach(() => {
    mockPrisma = {
      user: {
        findUnique: vi.fn(),
        findFirst: vi.fn(),
        create: vi.fn(),
        update: vi.fn(),
      },
      fieldEncryption: {
        computeHash: vi.fn((value: string) => `hash_${value.toLowerCase().trim()}`),
      },
    };
    repository = new PrismaUserRepository(mockPrisma as any);
  });

  describe('findById', () => {
    it('returns null when user not found', async () => {
      mockPrisma.user.findUnique.mockResolvedValue(null);
      const result = await repository.findById('non-existent');
      expect(result).toBeNull();
      expect(mockPrisma.user.findUnique).toHaveBeenCalledWith({ where: { id: 'non-existent' } });
    });

    it('returns domain entity when user is found', async () => {
      mockPrisma.user.findUnique.mockResolvedValue(mockPrismaUser);
      const result = await repository.findById('user-1');
      expect(result).not.toBeNull();
      expect(result!.id).toBe('user-1');
    });
  });

  describe('findByPhone', () => {
    it('returns null when user not found', async () => {
      mockPrisma.user.findUnique.mockResolvedValue(null);
      const result = await repository.findByPhone('+84912345678');
      expect(result).toBeNull();
      // With encryption enabled, should query by phoneHash
      expect(mockPrisma.user.findUnique).toHaveBeenCalledWith({
        where: { phoneHash: 'hash_+84912345678' },
      });
    });

    it('returns domain entity when user is found', async () => {
      mockPrisma.user.findUnique.mockResolvedValue(mockPrismaUser);
      const result = await repository.findByPhone('+84912345678');
      expect(result).not.toBeNull();
    });

    it('falls back to plaintext search when encryption disabled', async () => {
      mockPrisma.fieldEncryption.computeHash.mockReturnValue(null);
      mockPrisma.user.findFirst.mockResolvedValue(null);
      const result = await repository.findByPhone('+84912345678');
      expect(result).toBeNull();
      expect(mockPrisma.user.findFirst).toHaveBeenCalledWith({
        where: { phone: '+84912345678' },
      });
    });
  });

  describe('findByEmail', () => {
    it('returns null when user not found', async () => {
      mockPrisma.user.findUnique.mockResolvedValue(null);
      const result = await repository.findByEmail('test@example.com');
      expect(result).toBeNull();
      // With encryption enabled, should query by emailHash
      expect(mockPrisma.user.findUnique).toHaveBeenCalledWith({
        where: { emailHash: 'hash_test@example.com' },
      });
    });

    it('falls back to plaintext search when encryption disabled', async () => {
      mockPrisma.fieldEncryption.computeHash.mockReturnValue(null);
      mockPrisma.user.findFirst.mockResolvedValue(null);
      const result = await repository.findByEmail('test@example.com');
      expect(result).toBeNull();
      expect(mockPrisma.user.findFirst).toHaveBeenCalledWith({
        where: { email: 'test@example.com' },
      });
    });
  });

  describe('save', () => {
    it('calls prisma.user.create with correct data', async () => {
      const entity = {
        id: 'user-1',
        email: { value: 'test@example.com' },
        phone: { value: '+84912345678' },
        passwordHash: { value: '$2b$10$hash' },
        fullName: 'Nguyen Van A',
        avatarUrl: null,
        role: 'BUYER',
        kycStatus: 'NONE',
        kycData: null,
        isActive: true,
      };

      mockPrisma.user.create.mockResolvedValue(undefined);
      await repository.save(entity as any);

      expect(mockPrisma.user.create).toHaveBeenCalledWith({
        data: expect.objectContaining({
          id: 'user-1',
          phone: '+84912345678',
          email: 'test@example.com',
          role: 'BUYER',
        }),
      });
    });
  });

  describe('update', () => {
    it('calls prisma.user.update with correct data', async () => {
      const entity = {
        id: 'user-1',
        email: null,
        phone: { value: '+84912345678' },
        passwordHash: null,
        fullName: 'Updated Name',
        avatarUrl: null,
        role: 'BUYER',
        kycStatus: 'NONE',
        kycData: null,
        isActive: true,
      };

      mockPrisma.user.update.mockResolvedValue(undefined);
      await repository.update(entity as any);

      expect(mockPrisma.user.update).toHaveBeenCalledWith({
        where: { id: 'user-1' },
        data: expect.objectContaining({
          phone: '+84912345678',
          email: null,
          fullName: 'Updated Name',
        }),
      });
    });
  });
});
