import { describe, it, expect, vi, beforeEach } from 'vitest';
import { PrismaRefreshTokenRepository } from '../repositories/prisma-refresh-token.repository';

describe('PrismaRefreshTokenRepository', () => {
  let repository: PrismaRefreshTokenRepository;
  let mockPrisma: {
    refreshToken: {
      create: ReturnType<typeof vi.fn>;
      findUnique: ReturnType<typeof vi.fn>;
      updateMany: ReturnType<typeof vi.fn>;
      deleteMany: ReturnType<typeof vi.fn>;
    };
  };

  const mockTokenRecord = {
    id: 'token-1',
    userId: 'user-1',
    token: 'refresh-token-hash',
    family: 'family-uuid',
    expiresAt: new Date('2025-01-01'),
    revokedAt: null,
    createdAt: new Date('2024-01-01'),
  };

  beforeEach(() => {
    mockPrisma = {
      refreshToken: {
        create: vi.fn(),
        findUnique: vi.fn(),
        updateMany: vi.fn(),
        deleteMany: vi.fn(),
      },
    };
    repository = new PrismaRefreshTokenRepository(mockPrisma as any);
  });

  describe('create', () => {
    it('calls prisma.refreshToken.create with correct data', async () => {
      const input = {
        userId: 'user-1',
        token: 'new-token',
        family: 'new-family',
        expiresAt: new Date('2025-06-01'),
        revokedAt: null,
      };
      mockPrisma.refreshToken.create.mockResolvedValue({ ...mockTokenRecord, ...input });

      const result = await repository.create(input);

      expect(mockPrisma.refreshToken.create).toHaveBeenCalledWith({
        data: {
          userId: 'user-1',
          token: 'new-token',
          family: 'new-family',
          expiresAt: input.expiresAt,
          revokedAt: null,
        },
      });
      expect(result.token).toBe('new-token');
    });
  });

  describe('findByToken', () => {
    it('returns token record when found', async () => {
      mockPrisma.refreshToken.findUnique.mockResolvedValue(mockTokenRecord);
      const result = await repository.findByToken('refresh-token-hash');
      expect(result).toEqual(mockTokenRecord);
      expect(mockPrisma.refreshToken.findUnique).toHaveBeenCalledWith({
        where: { token: 'refresh-token-hash' },
      });
    });

    it('returns null when token not found', async () => {
      mockPrisma.refreshToken.findUnique.mockResolvedValue(null);
      const result = await repository.findByToken('non-existent');
      expect(result).toBeNull();
    });
  });

  describe('revokeByFamily', () => {
    it('updates all tokens in family with revokedAt', async () => {
      mockPrisma.refreshToken.updateMany.mockResolvedValue({ count: 3 });
      await repository.revokeByFamily('family-uuid');

      expect(mockPrisma.refreshToken.updateMany).toHaveBeenCalledWith({
        where: { family: 'family-uuid', revokedAt: null },
        data: { revokedAt: expect.any(Date) },
      });
    });
  });

  describe('revokeAllForUser', () => {
    it('updates all tokens for user with revokedAt', async () => {
      mockPrisma.refreshToken.updateMany.mockResolvedValue({ count: 5 });
      await repository.revokeAllForUser('user-1');

      expect(mockPrisma.refreshToken.updateMany).toHaveBeenCalledWith({
        where: { userId: 'user-1', revokedAt: null },
        data: { revokedAt: expect.any(Date) },
      });
    });
  });

  describe('deleteExpired', () => {
    it('deletes expired tokens and returns count', async () => {
      mockPrisma.refreshToken.deleteMany.mockResolvedValue({ count: 10 });
      const result = await repository.deleteExpired();
      expect(result).toBe(10);
      expect(mockPrisma.refreshToken.deleteMany).toHaveBeenCalledWith({
        where: { expiresAt: { lt: expect.any(Date) } },
      });
    });

    it('returns 0 when no expired tokens', async () => {
      mockPrisma.refreshToken.deleteMany.mockResolvedValue({ count: 0 });
      const result = await repository.deleteExpired();
      expect(result).toBe(0);
    });
  });
});
