import { beforeEach, describe, expect, it, vi } from 'vitest'

vi.mock('../../lib/prisma', () => ({ prisma: {} }))
vi.mock('../../lib/redis', () => ({ redis: { on: vi.fn(), get: vi.fn(), set: vi.fn(), del: vi.fn(), quit: vi.fn() } }))

vi.mock('./vehicle.repo')
vi.mock('../../lib/storage', () => ({ uploadImage: vi.fn() }))

import * as repo from './vehicle.repo'
import * as service from './vehicle.service'

const vehicle = {
  id: 'vehicle_1',
  companyId: 'company_1',
  make: 'Toyota',
  model: 'Corolla',
  year: 2024,
  dailyRate: 4000,
  photos: [],
  pickupLocations: [],
  allowDifferentDropoff: false,
  dropoffLocations: [],
  createdAt: new Date('2026-01-01T00:00:00.000Z'),
  updatedAt: new Date('2026-01-02T00:00:00.000Z'),
}

const config = {
  id: 'pricing_config_1',
  vehicleId: 'vehicle_1',
  pricingMode: 'MANUAL',
  baseDailyRate: 4000,
  weeklyRate: 24000,
  weekendRate: null,
  holidayRate: null,
  monthlyRate: null,
  longTermDailyRate: null,
  minimumDailyRate: null,
  maximumDailyRate: null,
  maxDailyPriceMovementPct: 10,
  automaticPricingEnabled: false,
  approvalRequired: false,
  rules: [],
  history: [],
  createdAt: new Date('2026-01-01T00:00:00.000Z'),
  updatedAt: new Date('2026-01-02T00:00:00.000Z'),
}

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

describe('vehicle pricing service boundaries', () => {
  it('creates a default pricing configuration from the vehicle when one does not exist', async () => {
    vi.mocked(repo.findById).mockResolvedValue(vehicle as any)
    vi.mocked(repo.findPricingConfiguration).mockResolvedValue(null as any)
    vi.mocked(repo.createPricingConfiguration).mockResolvedValue(config as any)

    const result = await service.getVehiclePricing('vehicle_1', 'company_1')

    expect(repo.createPricingConfiguration).toHaveBeenCalledWith(expect.objectContaining({
      vehicleId: 'vehicle_1',
      pricingMode: 'MANUAL',
      baseDailyRate: 4000,
      weeklyRate: 28000,
    }))
    expect(result.configuration.baseDailyRate).toBe(4000)
    expect(result.preview.next14Days).toHaveLength(14)
  })

  it('returns a transient pricing configuration when pricing tables are missing', async () => {
    vi.mocked(repo.findById).mockResolvedValue(vehicle as any)
    vi.mocked(repo.findPricingConfiguration).mockRejectedValue({ code: 'P2021' })

    const result = await service.getVehiclePricing('vehicle_1', 'company_1')

    expect(result.configuration.id).toBe('transient-vehicle_1')
    expect(result.configuration.baseDailyRate).toBe(4000)
    expect(repo.createPricingConfiguration).not.toHaveBeenCalled()
  })

  it('rejects pricing configuration bounds before persisting invalid rates', async () => {
    vi.mocked(repo.findById).mockResolvedValue(vehicle as any)
    vi.mocked(repo.findPricingConfiguration).mockResolvedValue(config as any)

    await expect(service.updateVehiclePricing('vehicle_1', 'company_1', 'employee_1', {
      pricingMode: 'MANUAL',
      baseDailyRate: 4000,
      minimumDailyRate: 5000,
      maximumDailyRate: 3000,
      maxDailyPriceMovementPct: 10,
      automaticPricingEnabled: false,
      approvalRequired: false,
    })).rejects.toThrow('Daily minimum rate must be less than or equal to the maximum rate')

    expect(repo.updatePricingConfiguration).not.toHaveBeenCalled()
    expect(repo.createPriceHistory).not.toHaveBeenCalled()
  })

  it('syncs vehicle daily rate and writes price history when pricing configuration changes', async () => {
    const updatedConfig = { ...config, baseDailyRate: 4500, weeklyRate: 27000 }
    vi.mocked(repo.findById).mockResolvedValue(vehicle as any)
    vi.mocked(repo.findPricingConfiguration)
      .mockResolvedValueOnce(config as any)
      .mockResolvedValueOnce(updatedConfig as any)
    vi.mocked(repo.updatePricingConfiguration).mockResolvedValue(updatedConfig as any)
    vi.mocked(repo.updateById).mockResolvedValue({ ...vehicle, dailyRate: 4500 } as any)
    vi.mocked(repo.createPriceHistory).mockResolvedValue({ id: 'history_1' } as any)

    await service.updateVehiclePricing('vehicle_1', 'company_1', 'employee_1', {
      pricingMode: 'MANUAL',
      baseDailyRate: 4500,
      weeklyRate: 27000,
      maxDailyPriceMovementPct: 10,
      automaticPricingEnabled: false,
      approvalRequired: false,
      note: 'Seasonal update',
    })

    expect(repo.updatePricingConfiguration).toHaveBeenCalledWith('pricing_config_1', expect.objectContaining({ baseDailyRate: 4500 }))
    expect(repo.updateById).toHaveBeenCalledWith('vehicle_1', { dailyRate: 4500 })
    expect(repo.createPriceHistory).toHaveBeenCalledWith(expect.objectContaining({
      source: 'CONFIG_UPDATE',
      changedByEmployeeId: 'employee_1',
      previousDailyRate: 4000,
      nextDailyRate: 4500,
      note: 'Seasonal update',
    }))
  })

  it('rejects pricing rules that do not define any pricing effect', async () => {
    vi.mocked(repo.findById).mockResolvedValue(vehicle as any)
    vi.mocked(repo.findPricingConfiguration).mockResolvedValue(config as any)

    await expect(service.createVehiclePricingRule('vehicle_1', 'company_1', 'employee_1', {
      name: 'Empty rule',
      ruleType: 'DATE_RANGE',
      startDate: '2026-07-01',
      endDate: '2026-07-10',
      isActive: true,
      sortOrder: 0,
    })).rejects.toThrow('Pricing rules must define at least one rate, bound, or adjustment')

    expect(repo.createPricingRule).not.toHaveBeenCalled()
  })

  it('rejects pricing rules whose end date is before the start date', async () => {
    vi.mocked(repo.findById).mockResolvedValue(vehicle as any)
    vi.mocked(repo.findPricingConfiguration).mockResolvedValue(config as any)

    await expect(service.createVehiclePricingRule('vehicle_1', 'company_1', 'employee_1', {
      name: 'Broken rule',
      ruleType: 'DATE_RANGE',
      startDate: '2026-07-10',
      endDate: '2026-07-01',
      dailyRate: 5000,
      isActive: true,
      sortOrder: 0,
    })).rejects.toThrow('Rule end date must be on or after the start date')
  })

  it('deletes an existing pricing rule and records the rollback target in history', async () => {
    const rule = {
      id: 'rule_1',
      name: 'Weekend bump',
      ruleType: 'WEEKEND',
      startDate: new Date('2026-07-01T00:00:00.000Z'),
      dailyRate: 5200,
      weeklyRate: null,
    }
    vi.mocked(repo.findById).mockResolvedValue(vehicle as any)
    vi.mocked(repo.findPricingConfiguration)
      .mockResolvedValueOnce(config as any)
      .mockResolvedValueOnce({ ...config, rules: [] } as any)
    vi.mocked(repo.findPricingRule).mockResolvedValue(rule as any)
    vi.mocked(repo.deletePricingRule).mockResolvedValue({ count: 1 } as any)
    vi.mocked(repo.createPriceHistory).mockResolvedValue({ id: 'history_1' } as any)

    await service.deleteVehiclePricingRule('vehicle_1', 'company_1', 'rule_1', 'employee_1')

    expect(repo.deletePricingRule).toHaveBeenCalledWith('rule_1', 'pricing_config_1')
    expect(repo.createPriceHistory).toHaveBeenCalledWith(expect.objectContaining({
      source: 'RULE_DELETED',
      previousDailyRate: 5200,
      nextDailyRate: 4000,
      note: 'Deleted pricing rule: Weekend bump',
    }))
  })
})
