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('../../middleware/requireCompanyAuth', () => {
  const auth = (req: any, _res: any, next: any) => {
    req.companyId = 'company_1'
    req.company = { id: 'company_1' }
    req.employee = { id: 'employee_1', role: 'OWNER', companyId: 'company_1' }
    next()
  }
  return { requireCompanyAuth: auth, requireCompanyDocumentAuth: auth }
})

vi.mock('../../middleware/requireTenant', () => ({
  requireTenant: (_req: any, _res: any, next: any) => next(),
}))

vi.mock('../../middleware/requireSubscription', () => ({
  requireSubscription: (_req: any, _res: any, next: any) => next(),
}))

vi.mock('../../middleware/requireRole', () => ({
  requireRole: () => (_req: any, _res: any, next: any) => next(),
}))

vi.mock('../../middleware/requireRenterAuth', () => ({
  requireRenterAuth: (req: any, _res: any, next: any) => {
    req.renterId = 'renter_1'
    next()
  },
  optionalRenterAuth: (req: any, _res: any, next: any) => {
    req.renterId = req.renterId ?? undefined
    next()
  },
}))

vi.mock('../../modules/offers/offer.service', () => ({
  listOffers: vi.fn(),
  getOffer: vi.fn(),
  createOffer: vi.fn(),
  updateOffer: vi.fn(),
  deleteOffer: vi.fn(),
  setOfferActive: vi.fn(),
  getOfferStats: vi.fn(),
}))

vi.mock('../../modules/notifications/notification.service', () => ({
  listCompany: vi.fn(),
  listCompanyHistory: vi.fn(),
  countUnread: vi.fn(),
  markRead: vi.fn(),
  markAllRead: vi.fn(),
  getPreferences: vi.fn(),
  setPreferences: vi.fn(),
  listRenter: vi.fn(),
  markRenterRead: vi.fn(),
  markAllRenterRead: vi.fn(),
  getRenterPreferences: vi.fn(),
  setRenterPreferences: vi.fn(),
}))

vi.mock('../../modules/team/team.service', () => ({
  getMembers: vi.fn(),
  getMemberStats: vi.fn(),
  inviteEmployee: vi.fn(),
  updateEmployeeRole: vi.fn(),
  deactivateEmployee: vi.fn(),
  reactivateEmployee: vi.fn(),
  removeEmployee: vi.fn(),
}))

import request from 'supertest'
import { createApp } from '../../app'
import * as offerService from '../../modules/offers/offer.service'
import * as notificationService from '../../modules/notifications/notification.service'
import * as teamService from '../../modules/team/team.service'

const app = createApp()

describe('operations API contracts', () => {
  beforeEach(() => {
    vi.clearAllMocks()
    vi.mocked(offerService.listOffers).mockResolvedValue([{ id: 'offer_1' }] as never)
    vi.mocked(offerService.createOffer).mockResolvedValue({ id: 'offer_1' } as never)
    vi.mocked(offerService.updateOffer).mockResolvedValue({ id: 'offer_1', title: 'Updated' } as never)
    vi.mocked(offerService.getOfferStats).mockResolvedValue({ redemptions: 2 } as never)
    vi.mocked(notificationService.countUnread).mockResolvedValue(7 as never)
    vi.mocked(notificationService.listCompanyHistory).mockResolvedValue([{ id: 'history_1' }] as never)
    vi.mocked(notificationService.getPreferences).mockResolvedValue([] as never)
    vi.mocked(notificationService.listRenter).mockResolvedValue([{ id: 'renter_notification_1' }] as never)
    vi.mocked(teamService.getMemberStats).mockResolvedValue({ total: 3, active: 2, pending: 1, inactive: 0 } as never)
    vi.mocked(teamService.inviteEmployee).mockResolvedValue({ employee: { id: 'employee_2' }, invitationId: 'employee_2', invitedBy: 'employee_1' } as never)
  })

  it('GET /api/v1/offers forwards boolean-like query strings without the router inventing semantics', async () => {
    const res = await request(app).get('/api/v1/offers?active=true&public=false')

    expect(res.status).toBe(200)
    expect(res.body).toEqual({ data: [{ id: 'offer_1' }] })
    expect(offerService.listOffers).toHaveBeenCalledWith('company_1', { active: 'true', public: 'false' })
  })

  it('POST /api/v1/offers validates payloads and separates vehicleIds from offer body', async () => {
    const payload = {
      title: 'Long weekend deal',
      type: 'PERCENTAGE',
      discountValue: 15,
      appliesToAll: false,
      categories: ['SUV'],
      validFrom: '2026-07-01T00:00:00.000Z',
      validUntil: '2026-07-31T23:59:59.000Z',
      vehicleIds: ['vehicle_1'],
    }

    const res = await request(app).post('/api/v1/offers').send(payload)

    expect(res.status).toBe(201)
    expect(res.body).toEqual({ data: { id: 'offer_1' } })
    expect(offerService.createOffer).toHaveBeenCalledWith('company_1', expect.not.objectContaining({ vehicleIds: expect.anything() }), ['vehicle_1'])
  })

  it('PATCH /api/v1/offers/:id validates route params and delegates partial updates', async () => {
    const res = await request(app).patch('/api/v1/offers/offer_1').send({ title: 'Updated', vehicleIds: [] })

    expect(res.status).toBe(200)
    expect(offerService.updateOffer).toHaveBeenCalledWith('offer_1', 'company_1', { title: 'Updated' }, [])
  })

  it('GET /api/v1/offers/:id/stats returns the service stats in the standard envelope', async () => {
    const res = await request(app).get('/api/v1/offers/offer_1/stats')

    expect(res.status).toBe(200)
    expect(res.body).toEqual({ data: { redemptions: 2 } })
    expect(offerService.getOfferStats).toHaveBeenCalledWith('offer_1', 'company_1')
  })

  it('GET /api/v1/notifications/unread-count exposes only the numeric unread count', async () => {
    const res = await request(app).get('/api/v1/notifications/unread-count')

    expect(res.status).toBe(200)
    expect(res.body).toEqual({ data: { unread: 7 } })
  })

  it('GET /api/v1/notifications/history coerces limit and passes history filters to the service', async () => {
    const res = await request(app).get('/api/v1/notifications/history?channel=EMAIL&status=FAILED&limit=25')

    expect(res.status).toBe(200)
    expect(notificationService.listCompanyHistory).toHaveBeenCalledWith('company_1', { channel: 'EMAIL', status: 'FAILED', limit: 25 })
  })

  it('PATCH /api/v1/notifications/company/preferences validates preference arrays for the current employee', async () => {
    const prefs = [{ notificationType: 'BOOKING_CONFIRMED', channel: 'EMAIL', enabled: true }]
    const res = await request(app).patch('/api/v1/notifications/company/preferences').send(prefs)

    expect(res.status).toBe(200)
    expect(res.body).toEqual({ data: { success: true } })
    expect(notificationService.setPreferences).toHaveBeenCalledWith('employee_1', prefs)
  })

  it('GET /api/v1/notifications/renter uses renter identity instead of company tenant middleware', async () => {
    const res = await request(app).get('/api/v1/notifications/renter')

    expect(res.status).toBe(200)
    expect(res.body).toEqual({ data: [{ id: 'renter_notification_1' }] })
    expect(notificationService.listRenter).toHaveBeenCalledWith('renter_1')
  })

  it('GET /api/v1/team/stats returns computed team stats from the team facade', async () => {
    const res = await request(app).get('/api/v1/team/stats')

    expect(res.status).toBe(200)
    expect(res.body).toEqual({ data: { total: 3, active: 2, pending: 1, inactive: 0 } })
  })

  it('POST /api/v1/team/invite validates invite payloads and wraps the created invitation', async () => {
    const res = await request(app).post('/api/v1/team/invite').send({
      firstName: 'Aya',
      lastName: 'Benali',
      email: 'aya@example.com',
      role: 'AGENT',
    })

    expect(res.status).toBe(201)
    expect(res.body).toEqual({ data: { data: { employee: { id: 'employee_2' }, invitationId: 'employee_2', invitedBy: 'employee_1' } } })
    expect(teamService.inviteEmployee).toHaveBeenCalledWith('company_1', 'employee_1', {
      firstName: 'Aya',
      lastName: 'Benali',
      email: 'aya@example.com',
      role: 'AGENT',
    })
  })
})
