/**
 * Unit tests for resource service
 * 
 * This file demonstrates testing patterns for service functions with database operations.
 * See docs/TEST_EXAMPLES.md for more examples.
 */

import { describe, it, expect, vi, beforeEach } from 'vitest'
import { getResources, getResource, createResource } from './resource'
import { AppErrors } from '../lib/errors'
import { Context } from '../types/context'

// Mock dependencies
vi.mock('../lib/errors')

describe('resource service', () => {
  let mockContext: Context
  let mockDb: any

  beforeEach(() => {
    vi.clearAllMocks()

    mockDb = {
      query: vi.fn()
    }

    mockContext = {
      user: {
        id: '1',
        email: 'user@example.com',
        name: 'Test User',
        role: 'USER',
        createdAt: new Date('2024-01-01'),
        updatedAt: new Date('2024-01-01'),
      },
      db: mockDb,
      tenantContext: null,
    } as Context
  })

  describe('getResources', () => {
    it('should return resources with site information', async () => {
      // Arrange
      const mockRows = [
        {
          id: '1',
          name: 'VM-1',
          type: 'VM',
          status: 'RUNNING',
          site_id: 'site-1',
          tenant_id: null,
          metadata: null,
          created_at: new Date('2024-01-01'),
          updated_at: new Date('2024-01-01'),
          site_id_full: 'site-1',
          site_name: 'Site 1',
          site_region: 'us-west',
          site_status: 'ACTIVE',
          site_metadata: null,
          site_created_at: new Date('2024-01-01'),
          site_updated_at: new Date('2024-01-01'),
        }
      ]

      mockDb.query.mockResolvedValue({
        rows: mockRows
      })

      // Act
      const result = await getResources(mockContext)

      // Assert
      expect(result).toHaveLength(1)
      expect(result[0].id).toBe('1')
      expect(result[0].name).toBe('VM-1')
      expect(result[0].site).toBeDefined()
      expect(result[0].site.name).toBe('Site 1')
      
      expect(mockDb.query).toHaveBeenCalled()
    })

    it('should filter resources by type', async () => {
      // Arrange
      mockDb.query.mockResolvedValue({
        rows: []
      })

      // Act
      await getResources(mockContext, { type: 'VM' })

      // Assert
      expect(mockDb.query).toHaveBeenCalledWith(
        expect.stringContaining("r.type = $"),
        expect.arrayContaining(['VM'])
      )
    })

    it('should filter resources by status', async () => {
      // Arrange
      mockDb.query.mockResolvedValue({
        rows: []
      })

      // Act
      await getResources(mockContext, { status: 'RUNNING' })

      // Assert
      expect(mockDb.query).toHaveBeenCalledWith(
        expect.stringContaining("r.status = $"),
        expect.arrayContaining(['RUNNING'])
      )
    })

    it('should enforce tenant isolation', async () => {
      // Arrange
      mockContext.tenantContext = {
        tenantId: 'tenant-1',
        isSystemAdmin: false,
      }

      mockDb.query.mockResolvedValue({
        rows: []
      })

      // Act
      await getResources(mockContext)

      // Assert
      expect(mockDb.query).toHaveBeenCalledWith(
        expect.stringContaining("r.tenant_id = $"),
        expect.arrayContaining(['tenant-1'])
      )
    })

    it('should allow system admins to see all resources', async () => {
      // Arrange
      mockContext.tenantContext = {
        tenantId: 'tenant-1',
        isSystemAdmin: true,
      }

      mockDb.query.mockResolvedValue({
        rows: []
      })

      // Act
      await getResources(mockContext)

      // Assert
      const queryCall = mockDb.query.mock.calls[0][0]
      expect(queryCall).not.toContain("r.tenant_id = $")
    })
  })

  describe('getResource', () => {
    it('should return a single resource by ID', async () => {
      // Arrange
      const mockRow = {
        id: '1',
        name: 'VM-1',
        type: 'VM',
        status: 'RUNNING',
        site_id: 'site-1',
        tenant_id: null,
        metadata: null,
        created_at: new Date('2024-01-01'),
        updated_at: new Date('2024-01-01'),
      }

      mockDb.query.mockResolvedValue({
        rows: [mockRow]
      })

      // Act
      const result = await getResource(mockContext, '1')

      // Assert
      expect(result).toBeDefined()
      expect(result.id).toBe('1')
      expect(result.name).toBe('VM-1')
      
      expect(mockDb.query).toHaveBeenCalledWith(
        expect.stringContaining('WHERE id = $1'),
        ['1']
      )
    })

    it('should throw NotFoundError for non-existent resource', async () => {
      // Arrange
      mockDb.query.mockResolvedValue({
        rows: []
      })

      // Act & Assert
      await expect(getResource(mockContext, 'nonexistent')).rejects.toThrow()
    })
  })

  describe('createResource', () => {
    it('should create a new resource', async () => {
      // Arrange
      const mockCreatedRow = {
        id: 'new-resource-id',
        name: 'New VM',
        type: 'VM',
        status: 'PENDING',
        site_id: 'site-1',
        tenant_id: null,
        metadata: null,
        created_at: new Date(),
        updated_at: new Date(),
      }

      // Mock site query
      mockDb.query
        .mockResolvedValueOnce({
          rows: [{ id: 'site-1', name: 'Site 1', region: 'us-west', status: 'ACTIVE' }]
        })
        .mockResolvedValueOnce({
          rows: [mockCreatedRow]
        })

      // Act
      const result = await createResource(mockContext, {
        name: 'New VM',
        type: 'VM',
        siteId: 'site-1',
      })

      // Assert
      expect(result).toBeDefined()
      expect(result.name).toBe('New VM')
      expect(mockDb.query).toHaveBeenCalledTimes(2) // Site lookup + insert
    })

    it('should set tenant_id from context when available', async () => {
      // Arrange
      mockContext.tenantContext = {
        tenantId: 'tenant-1',
        isSystemAdmin: false,
      }

      mockDb.query
        .mockResolvedValueOnce({
          rows: [{ id: 'site-1', name: 'Site 1', region: 'us-west', status: 'ACTIVE' }]
        })
        .mockResolvedValueOnce({
          rows: [{
            id: 'new-resource-id',
            tenant_id: 'tenant-1',
          }]
        })

      // Act
      await createResource(mockContext, {
        name: 'New VM',
        type: 'VM',
        siteId: 'site-1',
      })

      // Assert
      const insertQuery = mockDb.query.mock.calls[1][0]
      expect(insertQuery).toContain('tenant_id')
    })
  })
})

