import { describe, it, expect, vi, beforeEach } from 'vitest'
import * as repo from './vehicle.repo'
import * as service from './vehicle.service'

vi.mock('./vehicle.repo')
vi.mock('../../lib/storage', () => ({
  uploadImage: vi.fn().mockResolvedValue('http://localhost:4000/storage/vehicles/test.jpg'),
}))

const mockVehicle = {
  id: 'veh_1',
  companyId: 'comp_1',
  make: 'Toyota',
  model: 'Camry',
  year: 2022,
  licensePlate: 'ABC-123',
  status: 'AVAILABLE',
  isPublished: true,
  photos: [],
  dailyRate: 500,
  pickupLocations: [],
  allowDifferentDropoff: false,
  dropoffLocations: [],
}

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

describe('vehicle.service', () => {
  describe('createVehicle', () => {
    it('creates a vehicle with companyId', async () => {
      vi.mocked(repo.create).mockResolvedValue(mockVehicle as any)
      const result = await service.createVehicle({ make: 'Toyota', model: 'Camry', year: 2022, licensePlate: 'ABC-123', dailyRate: 500 }, 'comp_1')
      expect(repo.create).toHaveBeenCalledWith(expect.objectContaining({ companyId: 'comp_1' }))
      expect(result).toEqual(mockVehicle)
    })

    it('defaults dailyRate to zero when omitted', async () => {
      vi.mocked(repo.create).mockResolvedValue({ ...mockVehicle, dailyRate: 0 } as any)
      await service.createVehicle({ make: 'Toyota', model: 'Camry', year: 2022, licensePlate: 'ABC-123' }, 'comp_1')
      expect(repo.create).toHaveBeenCalledWith(expect.objectContaining({ companyId: 'comp_1', dailyRate: 0 }))
    })
  })

  describe('updateVehicle', () => {
    it('throws NotFoundError when vehicle does not belong to company', async () => {
      vi.mocked(repo.findFirst).mockResolvedValue(null)
      await expect(service.updateVehicle('veh_1', 'comp_1', { make: 'Honda' })).rejects.toThrow('Vehicle not found')
    })

    it('sets isPublished=false when status is MAINTENANCE', async () => {
      vi.mocked(repo.findFirst).mockResolvedValue(mockVehicle as any)
      vi.mocked(repo.updateById).mockResolvedValue({ ...mockVehicle, status: 'MAINTENANCE', isPublished: false } as any)
      await service.updateVehicle('veh_1', 'comp_1', { status: 'MAINTENANCE' })
      expect(repo.updateById).toHaveBeenCalledWith('veh_1', expect.objectContaining({ isPublished: false }))
    })

    it('sets isPublished=true when status is AVAILABLE', async () => {
      vi.mocked(repo.findFirst).mockResolvedValue({ ...mockVehicle, status: 'MAINTENANCE' } as any)
      vi.mocked(repo.updateById).mockResolvedValue({ ...mockVehicle, status: 'AVAILABLE', isPublished: true } as any)
      await service.updateVehicle('veh_1', 'comp_1', { status: 'AVAILABLE' })
      expect(repo.updateById).toHaveBeenCalledWith('veh_1', expect.objectContaining({ isPublished: true }))
    })
  })

  describe('uploadPhotos', () => {
    it('appends new photo URLs to existing photos', async () => {
      const vehicleWithPhotos = { ...mockVehicle, photos: ['http://localhost:4000/storage/vehicles/existing.jpg'] }
      vi.mocked(repo.findById).mockResolvedValue(vehicleWithPhotos as any)
      vi.mocked(repo.updateById).mockResolvedValue({ ...vehicleWithPhotos, photos: [...vehicleWithPhotos.photos, 'http://localhost:4000/storage/vehicles/test.jpg'] } as any)
      await service.uploadPhotos('veh_1', 'comp_1', [{ buffer: Buffer.from('') } as any])
      expect(repo.updateById).toHaveBeenCalledWith('veh_1', {
        photos: ['http://localhost:4000/storage/vehicles/existing.jpg', 'http://localhost:4000/storage/vehicles/test.jpg'],
      })
    })
  })

  describe('deletePhoto', () => {
    it('removes photo at the given index', async () => {
      const photos = ['url_0', 'url_1', 'url_2']
      vi.mocked(repo.findById).mockResolvedValue({ ...mockVehicle, photos } as any)
      vi.mocked(repo.updateById).mockResolvedValue({ ...mockVehicle, photos: ['url_0', 'url_2'] } as any)
      await service.deletePhoto('veh_1', 'comp_1', 1)
      expect(repo.updateById).toHaveBeenCalledWith('veh_1', { photos: ['url_0', 'url_2'] })
    })
  })

  describe('checkAvailability', () => {
    it('returns available=true when no conflicts', async () => {
      vi.mocked(repo.findReservationConflicts).mockResolvedValue([])
      vi.mocked(repo.findCalendarBlockConflicts).mockResolvedValue([])
      const result = await service.checkAvailability('veh_1', 'comp_1', '2025-06-01T00:00:00Z', '2025-06-07T00:00:00Z')
      expect(result.available).toBe(true)
      expect(result.conflicts).toHaveLength(0)
      expect(result.blocks).toHaveLength(0)
    })

    it('returns available=false when reservation conflict exists', async () => {
      vi.mocked(repo.findReservationConflicts).mockResolvedValue([{ id: 'res_1', startDate: new Date(), endDate: new Date(), status: 'CONFIRMED' }] as any)
      vi.mocked(repo.findCalendarBlockConflicts).mockResolvedValue([])
      const result = await service.checkAvailability('veh_1', 'comp_1', '2025-06-01T00:00:00Z', '2025-06-07T00:00:00Z')
      expect(result.available).toBe(false)
      expect(result.conflicts).toHaveLength(1)
    })

    it('returns available=false when calendar block conflict exists', async () => {
      vi.mocked(repo.findReservationConflicts).mockResolvedValue([])
      vi.mocked(repo.findCalendarBlockConflicts).mockResolvedValue([{ id: 'block_1', startDate: new Date(), endDate: new Date(), type: 'MANUAL', reason: null }] as any)
      const result = await service.checkAvailability('veh_1', 'comp_1', '2025-06-01T00:00:00Z', '2025-06-07T00:00:00Z')
      expect(result.available).toBe(false)
      expect(result.blocks).toHaveLength(1)
    })
  })

  describe('createCalendarBlock', () => {
    it('throws ValidationError when endDate is before startDate', async () => {
      await expect(
        service.createCalendarBlock('veh_1', 'comp_1', { startDate: '2025-06-07T00:00:00Z', endDate: '2025-06-01T00:00:00Z' }),
      ).rejects.toThrow('endDate must be after startDate')
    })

    it('creates a calendar block', async () => {
      vi.mocked(repo.findById).mockResolvedValue(mockVehicle as any)
      vi.mocked(repo.createCalendarBlock).mockResolvedValue({ id: 'block_1' } as any)
      const result = await service.createCalendarBlock('veh_1', 'comp_1', {
        startDate: '2025-06-01T00:00:00Z',
        endDate: '2025-06-07T00:00:00Z',
        type: 'MANUAL',
      })
      expect(result).toEqual({ id: 'block_1' })
    })
  })

  describe('getMaintenanceLogs', () => {
    it('throws NotFoundError when vehicle does not belong to the requesting company', async () => {
      vi.mocked(repo.findById).mockResolvedValue(null)
      await expect(service.getMaintenanceLogs('veh_1', 'other_company')).rejects.toThrow('Vehicle not found')
    })

    it('returns maintenance logs when vehicle belongs to the requesting company', async () => {
      const mockLogs = [{ id: 'log_1', vehicleId: 'veh_1', description: 'Oil change' }]
      vi.mocked(repo.findById).mockResolvedValue(mockVehicle as any)
      vi.mocked(repo.findMaintenanceLogs).mockResolvedValue(mockLogs as any)
      const result = await service.getMaintenanceLogs('veh_1', 'comp_1')
      expect(repo.findById).toHaveBeenCalledWith('veh_1', 'comp_1')
      expect(result).toEqual(mockLogs)
    })

    it('passes companyId to repo.findById for ownership validation', async () => {
      vi.mocked(repo.findById).mockResolvedValue(mockVehicle as any)
      vi.mocked(repo.findMaintenanceLogs).mockResolvedValue([])
      await service.getMaintenanceLogs('veh_1', 'comp_1')
      expect(repo.findById).toHaveBeenCalledWith('veh_1', 'comp_1')
    })
  })
})
