import { describe, it, expect, beforeEach, vi } from 'vitest'
import { PDFExportDatabaseClient } from '../db-client'
import type { Pool, QueryResult } from 'pg'

describe('PDFExportDatabaseClient', () => {
  let mockPool: Partial<Pool>
  let client: PDFExportDatabaseClient

  beforeEach(() => {
    mockPool = {
      query: vi.fn(),
    }

    client = new PDFExportDatabaseClient(mockPool as Pool)
  })

  describe('recordPDFGeneration', () => {
    it('should insert PDF generation record', async () => {
      const mockResult: QueryResult = {
        rows: [
          {
            id: 1,
            resource_type: 'asn',
            resource_value: 'AS13335',
            format: 'report',
            generated_at: new Date('2026-04-29T10:00:00Z'),
            expires_at: new Date('2026-04-29T10:05:00Z'),
            file_hash: 'abc123def456',
            file_size_bytes: 1024000,
            metadata: { format: 'report', generated_at: '2026-04-29T10:00:00Z' },
          },
        ],
        command: 'INSERT',
        rowCount: 1,
        oid: undefined,
        fields: [],
      }

      ;(mockPool.query as any).mockResolvedValueOnce(mockResult)

      const result = await client.recordPDFGeneration(
        'AS13335',
        'report',
        'abc123def456',
        1024000,
        { format: 'report', generated_at: '2026-04-29T10:00:00Z' }
      )

      expect(result.id).toBe(1)
      expect(result.resource_value).toBe('AS13335')
      expect(result.format).toBe('report')
      expect(mockPool.query).toHaveBeenCalled()
    })

    it('should handle ON CONFLICT for duplicate PDFs on same day', async () => {
      const mockResult: QueryResult = {
        rows: [
          {
            id: 2,
            resource_type: 'asn',
            resource_value: 'AS13335',
            format: 'report',
            generated_at: new Date('2026-04-29T10:05:00Z'),
            expires_at: new Date('2026-04-29T10:10:00Z'),
            file_hash: 'newHash789',
            file_size_bytes: 1050000,
            metadata: {},
          },
        ],
        command: 'UPDATE',
        rowCount: 1,
        oid: undefined,
        fields: [],
      }

      ;(mockPool.query as any).mockResolvedValueOnce(mockResult)

      const result = await client.recordPDFGeneration(
        'AS13335',
        'report',
        'newHash789',
        1050000,
        {}
      )

      expect(result.file_hash).toBe('newHash789')
      expect(result.file_size_bytes).toBe(1050000)
    })

    it('should pass correct parameters to query', async () => {
      ;(mockPool.query as any).mockResolvedValueOnce({
        rows: [{}],
        rowCount: 1,
      })

      await client.recordPDFGeneration('AS13335', 'report', 'hash123', 2048000, {
        custom: 'metadata',
      })

      const callArgs = (mockPool.query as any).mock.calls[0]
      expect(callArgs[1]).toEqual([
        'asn',
        'AS13335',
        'report',
        'hash123',
        2048000,
        JSON.stringify({ custom: 'metadata' }),
      ])
    })
  })

  describe('getPDFByHash', () => {
    it('should retrieve PDF by hash', async () => {
      const mockPdf = {
        id: 1,
        resource_type: 'asn',
        resource_value: 'AS13335',
        format: 'report',
        generated_at: new Date(),
        expires_at: new Date(Date.now() + 300000),
        file_hash: 'abc123def456',
        file_size_bytes: 1024000,
        metadata: {},
      }

      ;(mockPool.query as any).mockResolvedValueOnce({
        rows: [mockPdf],
        rowCount: 1,
      })

      const result = await client.getPDFByHash('abc123def456')

      expect(result).toEqual(mockPdf)
      expect((mockPool.query as any).mock.calls[0][1]).toEqual(['abc123def456'])
    })

    it('should return null if PDF not found', async () => {
      ;(mockPool.query as any).mockResolvedValueOnce({
        rows: [],
        rowCount: 0,
      })

      const result = await client.getPDFByHash('nonexistent')

      expect(result).toBeNull()
    })

    it('should return null if PDF expired', async () => {
      const mockExpiredPdf = {
        id: 1,
        resource_type: 'asn',
        resource_value: 'AS13335',
        format: 'report',
        generated_at: new Date(),
        expires_at: new Date(Date.now() - 1000), // Already expired
        file_hash: 'abc123def456',
        file_size_bytes: 1024000,
        metadata: {},
      }

      ;(mockPool.query as any).mockResolvedValueOnce({
        rows: [mockExpiredPdf],
        rowCount: 1,
      })

      // Note: The actual query filters expired PDFs, so this would return null in real usage
      const result = await client.getPDFByHash('abc123def456')

      // In the mock, we're returning the expired PDF, but in reality
      // the WHERE clause would filter it out
      expect(result?.expires_at.getTime()).toBeLessThan(new Date().getTime())
    })
  })

  describe('cleanupExpiredPDFs', () => {
    it('should delete expired PDFs and return count', async () => {
      ;(mockPool.query as any).mockResolvedValueOnce({
        rowCount: 5,
      })

      const result = await client.cleanupExpiredPDFs()

      expect(result).toBe(5)
    })

    it('should return 0 if no expired PDFs', async () => {
      ;(mockPool.query as any).mockResolvedValueOnce({
        rowCount: 0,
      })

      const result = await client.cleanupExpiredPDFs()

      expect(result).toBe(0)
    })

    it('should execute correct DELETE query', async () => {
      ;(mockPool.query as any).mockResolvedValueOnce({
        rowCount: 3,
      })

      await client.cleanupExpiredPDFs()

      const queryCall = (mockPool.query as any).mock.calls[0][0]
      expect(queryCall).toContain('DELETE FROM pdf_reports')
      expect(queryCall).toContain('WHERE expires_at <= NOW()')
    })
  })

  describe('getPDFStats', () => {
    it('should return PDF statistics', async () => {
      const mockStats = {
        total_records: 25,
        total_size_bytes: 52428800,
        expired_records: 3,
      }

      ;(mockPool.query as any).mockResolvedValueOnce({
        rows: [mockStats],
        rowCount: 1,
      })

      const result = await client.getPDFStats()

      expect(result.total_records).toBe(25)
      expect(result.total_size_bytes).toBe(52428800)
      expect(result.expired_records).toBe(3)
    })

    it('should handle zero records', async () => {
      const mockStats = {
        total_records: 0,
        total_size_bytes: 0,
        expired_records: 0,
      }

      ;(mockPool.query as any).mockResolvedValueOnce({
        rows: [mockStats],
        rowCount: 1,
      })

      const result = await client.getPDFStats()

      expect(result.total_records).toBe(0)
      expect(result.total_size_bytes).toBe(0)
      expect(result.expired_records).toBe(0)
    })

    it('should calculate aggregates correctly in SQL', async () => {
      ;(mockPool.query as any).mockResolvedValueOnce({
        rows: [{}],
      })

      await client.getPDFStats()

      const queryCall = (mockPool.query as any).mock.calls[0][0]
      const normalizedQuery = queryCall.replace(/\s+/g, ' ')
      expect(normalizedQuery).toContain('COUNT(*) as total_records')
      expect(normalizedQuery).toContain('total_size_bytes')
      expect(normalizedQuery).toContain(
        'COUNT(CASE WHEN expires_at <= NOW() THEN 1 END) as expired_records'
      )
    })

    it('should use COALESCE to handle null sums', async () => {
      ;(mockPool.query as any).mockResolvedValueOnce({
        rows: [{}],
      })

      await client.getPDFStats()

      const queryCall = (mockPool.query as any).mock.calls[0][0]
      expect(queryCall).toContain('COALESCE(SUM(file_size_bytes), 0)')
    })
  })

  describe('query error handling', () => {
    it('should propagate query errors', async () => {
      ;(mockPool.query as any).mockRejectedValueOnce(
        new Error('Database connection failed')
      )

      await expect(client.recordPDFGeneration('AS13335', 'report', 'hash', 1000)).rejects.toThrow(
        'Database connection failed'
      )
    })

    it('should handle missing result rows', async () => {
      ;(mockPool.query as any).mockResolvedValueOnce({
        rows: [],
        rowCount: 0,
      })

      const result = await client.recordPDFGeneration('AS13335', 'report', 'hash', 1000)

      expect(result).toBeUndefined()
    })
  })
})
