import request from 'supertest'
import { app } from '../src/index.js'
import { User, Service, Booking } from '../src/models/index.js'

describe('Booking Routes', () => {
  let userToken
  let providerToken
  let userId
  let providerId
  let serviceId

  beforeEach(async () => {
    await Booking.destroy({ where: {}, force: true })
    await Service.destroy({ where: {}, force: true })
    await User.destroy({ where: {}, force: true })

    // Create user
    const userRes = await request(app)
      .post('/api/auth/register')
      .send({
        name: 'User',
        email: 'user@example.com',
        password: 'password123',
        role: 'user'
      })
    userToken = userRes.headers['set-cookie'][0]
    userId = userRes.body.user.id

    // Create provider
    const providerRes = await request(app)
      .post('/api/auth/register')
      .send({
        name: 'Provider',
        email: 'provider@example.com',
        password: 'password123',
        role: 'provider'
      })
    providerToken = providerRes.headers['set-cookie'][0]
    providerId = providerRes.body.user.id

    // Create service
    const service = await Service.create({
      title: 'Test Service',
      description: 'Test description',
      price: 100,
      price_type: 'fixed',
      category: 'plumbing',
      provider_id: providerId,
      is_active: true,
      duration: 2
    })
    serviceId = service.id
  })

  const getAuthHeader = (token) => {
    return { Cookie: token }
  }

  describe('POST /api/bookings', () => {
    it('should create a booking successfully', async () => {
      const res = await request(app)
        .post('/api/bookings')
        .set('Cookie', userToken)
        .send({
          serviceId,
          scheduledAt: new Date(Date.now() + 86400000).toISOString(),
          address: '123 Main St',
          duration: 2,
          notes: 'Please come on time'
        })

      expect(res.statusCode).toBe(201)
      expect(res.body).toHaveProperty('booking')
      expect(res.body.booking).toHaveProperty('id')
      expect(res.body.booking.user_id).toBe(userId)
      expect(res.body.booking.provider_id).toBe(providerId)
      expect(res.body.booking.service_id).toBe(serviceId)
      expect(res.body.booking.status).toBe('pending')
    })

    it('should return 401 without authentication', async () => {
      const res = await request(app)
        .post('/api/bookings')
        .send({
          serviceId,
          scheduledAt: new Date(Date.now() + 86400000).toISOString(),
          address: '123 Main St'
        })

      expect(res.statusCode).toBe(401)
    })

    it('should return 404 for non-existent service', async () => {
      const res = await request(app)
        .post('/api/bookings')
        .set('Cookie', userToken)
        .send({
          serviceId: '00000000-0000-0000-0000-000000000000',
          scheduledAt: new Date(Date.now() + 86400000).toISOString(),
          address: '123 Main St'
        })

      expect(res.statusCode).toBe(404)
      expect(res.body.error).toBe('Service not found')
    })

    it('should return 400 for missing required fields', async () => {
      const res = await request(app)
        .post('/api/bookings')
        .set('Cookie', userToken)
        .send({})

      expect(res.statusCode).toBe(400)
    })

    it('should calculate price correctly for hourly service', async () => {
      const hourlyService = await Service.create({
        title: 'Hourly Service',
        description: 'Test',
        price: 50,
        price_type: 'hourly',
        category: 'cleaning',
        provider_id: providerId,
        is_active: true,
        duration: 1
      })

      const res = await request(app)
        .post('/api/bookings')
        .set('Cookie', userToken)
        .send({
          serviceId: hourlyService.id,
          scheduledAt: new Date(Date.now() + 86400000).toISOString(),
          address: '123 Main St',
          duration: 3
        })

      expect(res.statusCode).toBe(201)
      expect(res.body.booking.price).toBe(150) // 50 * 3
    })

    it('should use fixed price for fixed price service', async () => {
      const res = await request(app)
        .post('/api/bookings')
        .set('Cookie', userToken)
        .send({
          serviceId,
          scheduledAt: new Date(Date.now() + 86400000).toISOString(),
          address: '123 Main St',
          duration: 5
        })

      expect(res.statusCode).toBe(201)
      expect(Number(res.body.booking.price)).toBe(100) // fixed price
    })
  })

  describe('GET /api/bookings', () => {
    beforeEach(async () => {
      await Booking.bulkCreate([
        { user_id: userId, provider_id: providerId, service_id: serviceId, scheduled_at: new Date(Date.now() + 86400000), address: '123 Main St', duration: 2, price: 100, status: 'pending' },
        { user_id: userId, provider_id: providerId, service_id: serviceId, scheduled_at: new Date(Date.now() + 172800000), address: '456 Oak Ave', duration: 1, price: 50, status: 'accepted' },
        { user_id: userId, provider_id: providerId, service_id: serviceId, scheduled_at: new Date(Date.now() + 259200000), address: '789 Pine Rd', duration: 3, price: 150, status: 'completed' }
      ])
    })

    it('should return user bookings', async () => {
      const res = await request(app)
        .get('/api/bookings')
        .set('Cookie', userToken)

      expect(res.statusCode).toBe(200)
      expect(res.body.bookings).toHaveLength(3)
      expect(res.body.total).toBe(3)
    })

    it('should return provider bookings', async () => {
      const res = await request(app)
        .get('/api/bookings')
        .set('Cookie', providerToken)

      expect(res.statusCode).toBe(200)
      expect(res.body.bookings).toHaveLength(3)
    })

    it('should filter by status', async () => {
      const res = await request(app)
        .get('/api/bookings?status=pending')
        .set('Cookie', userToken)

      expect(res.statusCode).toBe(200)
      expect(res.body.bookings).toHaveLength(1)
      expect(res.body.bookings[0].status).toBe('pending')
    })

    it('should paginate results', async () => {
      const res = await request(app)
        .get('/api/bookings?page=1&limit=2')
        .set('Cookie', userToken)

      expect(res.statusCode).toBe(200)
      expect(res.body.bookings).toHaveLength(2)
      expect(res.body.page).toBe(1)
      expect(res.body.pages).toBe(2)
    })

    it('should include service and user details', async () => {
      const res = await request(app)
        .get('/api/bookings')
        .set('Cookie', userToken)

      expect(res.statusCode).toBe(200)
      expect(res.body.bookings[0]).toHaveProperty('service')
      expect(res.body.bookings[0]).toHaveProperty('user')
      expect(res.body.bookings[0]).toHaveProperty('provider')
    })
  })

  describe('GET /api/bookings/:id', () => {
    it('should return booking by id for user', async () => {
      const booking = await Booking.create({
        user_id: userId,
        provider_id: providerId,
        service_id: serviceId,
        scheduled_at: new Date(Date.now() + 86400000),
        address: '123 Main St',
        duration: 2,
        price: 100,
        status: 'pending'
      })

      const res = await request(app)
        .get(`/api/bookings/${booking.id}`)
        .set('Cookie', userToken)

      expect(res.statusCode).toBe(200)
      expect(res.body.booking).toHaveProperty('id', booking.id)
    })

    it('should return booking by id for provider', async () => {
      const booking = await Booking.create({
        user_id: userId,
        provider_id: providerId,
        service_id: serviceId,
        scheduled_at: new Date(Date.now() + 86400000),
        address: '123 Main St',
        duration: 2,
        price: 100,
        status: 'pending'
      })

      const res = await request(app)
        .get(`/api/bookings/${booking.id}`)
        .set('Cookie', providerToken)

      expect(res.statusCode).toBe(200)
      expect(res.body.booking).toHaveProperty('id', booking.id)
    })

    it('should return 404 for non-existent booking', async () => {
      const res = await request(app)
        .get('/api/bookings/00000000-0000-0000-0000-000000000000')
        .set('Cookie', userToken)

      expect(res.statusCode).toBe(404)
      expect(res.body.error).toBe('Booking not found')
    })

    it('should return 403 for unauthorized access', async () => {
      // Create another user
      const otherUserRes = await request(app)
        .post('/api/auth/register')
        .send({
          name: 'Other',
          email: 'other@example.com',
          password: 'password123',
          role: 'user'
        })
      const otherToken = otherUserRes.headers['set-cookie'][0]

      const booking = await Booking.create({
        user_id: userId,
        provider_id: providerId,
        service_id: serviceId,
        scheduled_at: new Date(Date.now() + 86400000),
        address: '123 Main St',
        duration: 2,
        price: 100,
        status: 'pending'
      })

      const res = await request(app)
        .get(`/api/bookings/${booking.id}`)
        .set('Cookie', otherToken)

      expect(res.statusCode).toBe(403)
      expect(res.body.error).toBe('Forbidden')
    })
  })

  describe('PUT /api/bookings/:id/status', () => {
    let booking

    beforeEach(async () => {
      booking = await Booking.create({
        user_id: userId,
        provider_id: providerId,
        service_id: serviceId,
        scheduled_at: new Date(Date.now() + 86400000),
        address: '123 Main St',
        duration: 2,
        price: 100,
        status: 'pending'
      })
    })

    it('should allow provider to accept booking', async () => {
      const res = await request(app)
        .put(`/api/bookings/${booking.id}/status`)
        .set('Cookie', providerToken)
        .send({ status: 'accepted' })

      expect(res.statusCode).toBe(200)
      expect(res.body.booking.status).toBe('accepted')
    })

    it('should allow provider to cancel booking', async () => {
      const res = await request(app)
        .put(`/api/bookings/${booking.id}/status`)
        .set('Cookie', providerToken)
        .send({ status: 'cancelled', reason: 'Not available' })

      expect(res.statusCode).toBe(200)
      expect(res.body.booking.status).toBe('cancelled')
      expect(res.body.booking.cancellation_reason).toBe('Not available')
    })

    it('should reject invalid status transition', async () => {
      // First accept the booking
      await booking.update({ status: 'accepted' })

      const res = await request(app)
        .put(`/api/bookings/${booking.id}/status`)
        .set('Cookie', providerToken)
        .send({ status: 'pending' })

      expect(res.statusCode).toBe(400)
      expect(res.body.error).toBe('Invalid status transition')
    })

    it('should return 404 for non-existent booking', async () => {
      const res = await request(app)
        .put('/api/bookings/00000000-0000-0000-0000-000000000000/status')
        .set('Cookie', providerToken)
        .send({ status: 'accepted' })

      expect(res.statusCode).toBe(404)
    })

    it('should return 403 without provider authentication', async () => {
      const res = await request(app)
        .put(`/api/bookings/${booking.id}/status`)
        .set('Cookie', userToken)
        .send({ status: 'accepted' })

      expect(res.statusCode).toBe(403)
    })

    it('should complete booking and set completed_at', async () => {
      await booking.update({ status: 'in_progress' })

      const res = await request(app)
        .put(`/api/bookings/${booking.id}/status`)
        .set('Cookie', providerToken)
        .send({ status: 'completed' })

      expect(res.statusCode).toBe(200)
      expect(res.body.booking.status).toBe('completed')
      expect(res.body.booking.completed_at).toBeDefined()
    })
  })
})

// afterAll handled by jest config forceExit