/**
 * @ai-summary Unit tests for DocumentsRepository
 * @ai-context Tests database layer with mocked pool
 */

import { DocumentsRepository } from '../../data/documents.repository';
import type { Pool } from 'pg';

describe('DocumentsRepository', () => {
  let repository: DocumentsRepository;
  let mockPool: jest.Mocked<Pool>;

  beforeEach(() => {
    mockPool = {
      query: jest.fn(),
    } as any;
    repository = new DocumentsRepository(mockPool);
  });

  describe('insert', () => {
    const mockDocumentData = {
      id: 'doc-123',
      user_id: 'user-123',
      vehicle_id: 'vehicle-123',
      document_type: 'insurance' as const,
      title: 'Test Document',
      notes: 'Test notes',
      details: { provider: 'Test Provider' },
      issued_date: '2024-01-01',
      expiration_date: '2024-12-31',
    };

    it('should insert document with all fields', async () => {
      const mockResult = { rows: [{ ...mockDocumentData, created_at: '2024-01-01T00:00:00Z' }] };
      mockPool.query.mockResolvedValue(mockResult);

      const result = await repository.insert(mockDocumentData);

      expect(mockPool.query).toHaveBeenCalledWith(
        expect.stringContaining('INSERT INTO documents'),
        [
          'doc-123',
          'user-123',
          'vehicle-123',
          'insurance',
          'Test Document',
          'Test notes',
          { provider: 'Test Provider' },
          '2024-01-01',
          '2024-12-31',
        ]
      );
      expect(result).toEqual(mockResult.rows[0]);
    });

    it('should insert document with null optional fields', async () => {
      const minimalData = {
        id: 'doc-123',
        user_id: 'user-123',
        vehicle_id: 'vehicle-123',
        document_type: 'registration' as const,
        title: 'Test Document',
      };
      const mockResult = { rows: [{ ...minimalData, notes: null, details: null }] };
      mockPool.query.mockResolvedValue(mockResult);

      const result = await repository.insert(minimalData);

      expect(mockPool.query).toHaveBeenCalledWith(
        expect.stringContaining('INSERT INTO documents'),
        [
          'doc-123',
          'user-123',
          'vehicle-123',
          'registration',
          'Test Document',
          null,
          null,
          null,
          null,
        ]
      );
      expect(result).toEqual(mockResult.rows[0]);
    });
  });

  describe('findById', () => {
    it('should find document by id and user', async () => {
      const mockDocument = { id: 'doc-123', user_id: 'user-123', title: 'Test' };
      mockPool.query.mockResolvedValue({ rows: [mockDocument] });

      const result = await repository.findById('doc-123', 'user-123');

      expect(mockPool.query).toHaveBeenCalledWith(
        'SELECT * FROM documents WHERE id = $1 AND user_id = $2 AND deleted_at IS NULL',
        ['doc-123', 'user-123']
      );
      expect(result).toEqual(mockDocument);
    });

    it('should return null if document not found', async () => {
      mockPool.query.mockResolvedValue({ rows: [] });

      const result = await repository.findById('doc-123', 'user-123');

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

  describe('listByUser', () => {
    const mockDocuments = [
      { id: 'doc-1', user_id: 'user-123', title: 'Doc 1' },
      { id: 'doc-2', user_id: 'user-123', title: 'Doc 2' },
    ];

    it('should list all user documents without filters', async () => {
      mockPool.query.mockResolvedValue({ rows: mockDocuments });

      const result = await repository.listByUser('user-123');

      expect(mockPool.query).toHaveBeenCalledWith(
        'SELECT * FROM documents WHERE user_id = $1 AND deleted_at IS NULL ORDER BY created_at DESC',
        ['user-123']
      );
      expect(result).toEqual(mockDocuments);
    });

    it('should list documents with vehicleId filter', async () => {
      mockPool.query.mockResolvedValue({ rows: [mockDocuments[0]] });

      const result = await repository.listByUser('user-123', { vehicleId: 'vehicle-123' });

      expect(mockPool.query).toHaveBeenCalledWith(
        'SELECT * FROM documents WHERE user_id = $1 AND deleted_at IS NULL AND vehicle_id = $2 ORDER BY created_at DESC',
        ['user-123', 'vehicle-123']
      );
      expect(result).toEqual([mockDocuments[0]]);
    });

    it('should list documents with type filter', async () => {
      mockPool.query.mockResolvedValue({ rows: [mockDocuments[0]] });

      const result = await repository.listByUser('user-123', { type: 'insurance' });

      expect(mockPool.query).toHaveBeenCalledWith(
        'SELECT * FROM documents WHERE user_id = $1 AND deleted_at IS NULL AND document_type = $2 ORDER BY created_at DESC',
        ['user-123', 'insurance']
      );
      expect(result).toEqual([mockDocuments[0]]);
    });

    it('should list documents with expiresBefore filter', async () => {
      mockPool.query.mockResolvedValue({ rows: [mockDocuments[0]] });

      const result = await repository.listByUser('user-123', { expiresBefore: '2024-12-31' });

      expect(mockPool.query).toHaveBeenCalledWith(
        'SELECT * FROM documents WHERE user_id = $1 AND deleted_at IS NULL AND expiration_date <= $2 ORDER BY created_at DESC',
        ['user-123', '2024-12-31']
      );
      expect(result).toEqual([mockDocuments[0]]);
    });

    it('should list documents with multiple filters', async () => {
      mockPool.query.mockResolvedValue({ rows: [mockDocuments[0]] });

      const result = await repository.listByUser('user-123', {
        vehicleId: 'vehicle-123',
        type: 'insurance',
        expiresBefore: '2024-12-31',
      });

      expect(mockPool.query).toHaveBeenCalledWith(
        'SELECT * FROM documents WHERE user_id = $1 AND deleted_at IS NULL AND vehicle_id = $2 AND document_type = $3 AND expiration_date <= $4 ORDER BY created_at DESC',
        ['user-123', 'vehicle-123', 'insurance', '2024-12-31']
      );
      expect(result).toEqual([mockDocuments[0]]);
    });
  });

  describe('softDelete', () => {
    it('should soft delete document', async () => {
      mockPool.query.mockResolvedValue({ rows: [] });

      await repository.softDelete('doc-123', 'user-123');

      expect(mockPool.query).toHaveBeenCalledWith(
        'UPDATE documents SET deleted_at = NOW() WHERE id = $1 AND user_id = $2',
        ['doc-123', 'user-123']
      );
    });
  });

  describe('updateMetadata', () => {
    it('should update single field', async () => {
      const mockUpdated = { id: 'doc-123', title: 'Updated Title' };
      mockPool.query.mockResolvedValue({ rows: [mockUpdated] });

      const result = await repository.updateMetadata('doc-123', 'user-123', { title: 'Updated Title' });

      expect(mockPool.query).toHaveBeenCalledWith(
        'UPDATE documents SET title = $1 WHERE id = $2 AND user_id = $3 AND deleted_at IS NULL RETURNING *',
        ['Updated Title', 'doc-123', 'user-123']
      );
      expect(result).toEqual(mockUpdated);
    });

    it('should update multiple fields', async () => {
      const mockUpdated = { id: 'doc-123', title: 'Updated Title', notes: 'Updated notes' };
      mockPool.query.mockResolvedValue({ rows: [mockUpdated] });

      const result = await repository.updateMetadata('doc-123', 'user-123', {
        title: 'Updated Title',
        notes: 'Updated notes',
        details: { key: 'value' },
      });

      expect(mockPool.query).toHaveBeenCalledWith(
        'UPDATE documents SET title = $1, notes = $2, details = $3 WHERE id = $4 AND user_id = $5 AND deleted_at IS NULL RETURNING *',
        ['Updated Title', 'Updated notes', { key: 'value' }, 'doc-123', 'user-123']
      );
      expect(result).toEqual(mockUpdated);
    });

    it('should handle null values', async () => {
      const mockUpdated = { id: 'doc-123', notes: null };
      mockPool.query.mockResolvedValue({ rows: [mockUpdated] });

      const result = await repository.updateMetadata('doc-123', 'user-123', { notes: null });

      expect(mockPool.query).toHaveBeenCalledWith(
        'UPDATE documents SET notes = $1 WHERE id = $2 AND user_id = $3 AND deleted_at IS NULL RETURNING *',
        [null, 'doc-123', 'user-123']
      );
      expect(result).toEqual(mockUpdated);
    });

    it('should return existing record if no fields to update', async () => {
      const mockExisting = { id: 'doc-123', title: 'Existing' };
      mockPool.query.mockResolvedValue({ rows: [mockExisting] });

      const result = await repository.updateMetadata('doc-123', 'user-123', {});

      expect(mockPool.query).toHaveBeenCalledWith(
        'SELECT * FROM documents WHERE id = $1 AND user_id = $2 AND deleted_at IS NULL',
        ['doc-123', 'user-123']
      );
      expect(result).toEqual(mockExisting);
    });

    it('should return null if document not found', async () => {
      mockPool.query.mockResolvedValue({ rows: [] });

      const result = await repository.updateMetadata('doc-123', 'user-123', { title: 'New Title' });

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

  describe('updateStorageMeta', () => {
    it('should update storage metadata', async () => {
      const storageMeta = {
        storage_bucket: 'test-bucket',
        storage_key: 'test-key',
        file_name: 'test.pdf',
        content_type: 'application/pdf',
        file_size: 1024,
        file_hash: 'hash123',
      };
      const mockUpdated = { id: 'doc-123', ...storageMeta };
      mockPool.query.mockResolvedValue({ rows: [mockUpdated] });

      const result = await repository.updateStorageMeta('doc-123', 'user-123', storageMeta);

      expect(mockPool.query).toHaveBeenCalledWith(
        expect.stringContaining('UPDATE documents SET'),
        [
          'test-bucket',
          'test-key',
          'test.pdf',
          'application/pdf',
          1024,
          'hash123',
          'doc-123',
          'user-123',
        ]
      );
      expect(result).toEqual(mockUpdated);
    });

    it('should handle null file_hash', async () => {
      const storageMeta = {
        storage_bucket: 'test-bucket',
        storage_key: 'test-key',
        file_name: 'test.pdf',
        content_type: 'application/pdf',
        file_size: 1024,
      };
      mockPool.query.mockResolvedValue({ rows: [{ id: 'doc-123', ...storageMeta, file_hash: null }] });

      const result = await repository.updateStorageMeta('doc-123', 'user-123', storageMeta);

      expect(mockPool.query).toHaveBeenCalledWith(
        expect.stringContaining('UPDATE documents SET'),
        [
          'test-bucket',
          'test-key',
          'test.pdf',
          'application/pdf',
          1024,
          null,
          'doc-123',
          'user-123',
        ]
      );
    });

    it('should return null if document not found', async () => {
      const storageMeta = {
        storage_bucket: 'test-bucket',
        storage_key: 'test-key',
        file_name: 'test.pdf',
        content_type: 'application/pdf',
        file_size: 1024,
      };
      mockPool.query.mockResolvedValue({ rows: [] });

      const result = await repository.updateStorageMeta('doc-123', 'user-123', storageMeta);

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