import { describe, it, expect } from 'vitest'
import {
  validateDevice,
  validateUpdateDevice,
  validateUser,
  validateUpdateUser,
  validatePoi,
  validateUpdatePoi,
} from '../../server/utils/validation.js'

describe('validation', () => {
  describe('validateDevice', () => {
    it('validates valid device data', () => {
      const result = validateDevice({
        name: 'Test Device',
        device_type: 'traffic',
        lat: 40.7128,
        lng: -74.0060,
        stream_url: 'https://example.com/stream',
        source_type: 'mjpeg',
      })
      expect(result.valid).toBe(true)
      expect(result.data.device_type).toBe('traffic')
    })

    it.each([
      [{ name: 'Test', lat: 'invalid', lng: -74.0060 }, 'lat and lng required as finite numbers'],
      [null, 'body required'],
    ])('rejects invalid input: %j', (input, errorMsg) => {
      const result = validateDevice(input)
      expect(result.valid).toBe(false)
      expect(result.errors).toContain(errorMsg)
    })

    it('defaults device_type to feed', () => {
      const result = validateDevice({ name: 'Test', lat: 40.7128, lng: -74.0060 })
      expect(result.valid).toBe(true)
      expect(result.data.device_type).toBe('feed')
    })

    it('defaults stream_url to empty string', () => {
      const result = validateDevice({ name: 'Test', lat: 40.7128, lng: -74.0060 })
      expect(result.valid).toBe(true)
      expect(result.data.stream_url).toBe('')
    })

    it('defaults invalid source_type to mjpeg', () => {
      const result = validateDevice({
        name: 'Test',
        lat: 40.7128,
        lng: -74.0060,
        source_type: 'invalid',
      })
      expect(result.valid).toBe(true)
      expect(result.data.source_type).toBe('mjpeg')
    })

    it.each([
      [{ name: 'Test', lat: 40.7128, lng: -74.0060 }, null],
      [{ name: 'Test', lat: 40.7128, lng: -74.0060, config: { key: 'value' } }, '{"key":"value"}'],
      [{ name: 'Test', lat: 40.7128, lng: -74.0060, config: '{"key":"value"}' }, '{"key":"value"}'],
      [{ name: 'Test', lat: 40.7128, lng: -74.0060, config: null }, null],
    ])('handles config: %j -> %s', (input, expected) => {
      const result = validateDevice(input)
      expect(result.valid).toBe(true)
      expect(result.data.config).toBe(expected)
    })

    it('defaults vendor to null', () => {
      const result = validateDevice({ name: 'Test', lat: 40.7128, lng: -74.0060 })
      expect(result.valid).toBe(true)
      expect(result.data.vendor).toBeNull()
    })
  })

  describe('validateUpdateDevice', () => {
    it('validates partial updates', () => {
      const result = validateUpdateDevice({ name: 'Updated', lat: 40.7128 })
      expect(result.valid).toBe(true)
      expect(result.data).toMatchObject({ name: 'Updated', lat: 40.7128 })
    })

    it('allows empty updates', () => {
      const result = validateUpdateDevice({})
      expect(result.valid).toBe(true)
      expect(Object.keys(result.data).length).toBe(0)
    })

    it.each([
      [{ device_type: 'invalid' }, 'Invalid device_type'],
    ])('rejects invalid input: %j', (input, errorMsg) => {
      const result = validateUpdateDevice(input)
      expect(result.valid).toBe(false)
      expect(result.errors).toContain(errorMsg)
    })

    it.each([
      [{ name: 'Test' }, undefined],
      [{ device_type: 'traffic' }, 'traffic'],
    ])('handles device_type: %j -> %s', (input, expected) => {
      const result = validateUpdateDevice(input)
      expect(result.valid).toBe(true)
      expect(result.data.device_type).toBe(expected)
    })

    it.each([
      [{ vendor: null }, null],
      [{ vendor: '' }, null],
      [{ vendor: 'Test Vendor' }, 'Test Vendor'],
    ])('handles vendor: %j -> %s', (input, expected) => {
      const result = validateUpdateDevice(input)
      expect(result.valid).toBe(true)
      expect(result.data.vendor).toBe(expected)
    })

    it.each([
      [{ config: { key: 'value' } }, '{"key":"value"}'],
      [{ config: '{"key":"value"}' }, '{"key":"value"}'],
      [{ config: null }, null],
      [{ config: undefined }, undefined],
      [{ name: 'Test' }, undefined],
    ])('handles config: %j', (input, expected) => {
      const result = validateUpdateDevice(input)
      expect(result.valid).toBe(true)
      expect(result.data.config).toBe(expected)
    })

    it('handles all field types', () => {
      const result = validateUpdateDevice({
        name: 'Test',
        device_type: 'traffic',
        vendor: 'Vendor',
        lat: 40.7128,
        lng: -74.0060,
        stream_url: 'https://example.com',
        source_type: 'hls',
        config: { key: 'value' },
      })
      expect(result.valid).toBe(true)
      expect(result.data).toMatchObject({
        name: 'Test',
        device_type: 'traffic',
        vendor: 'Vendor',
        lat: 40.7128,
        lng: -74.0060,
        stream_url: 'https://example.com',
        source_type: 'hls',
        config: '{"key":"value"}',
      })
    })

    it.each([
      ['source_type'],
      ['lat'],
      ['lng'],
      ['stream_url'],
    ])('handles %s undefined in updates', (field) => {
      const result = validateUpdateDevice({ name: 'Test' })
      expect(result.valid).toBe(true)
      expect(result.data[field]).toBeUndefined()
    })
  })

  describe('validateUser', () => {
    it('validates valid user data', () => {
      const result = validateUser({
        identifier: 'testuser',
        password: 'password123',
        role: 'admin',
      })
      expect(result.valid).toBe(true)
      expect(result.data.identifier).toBe('testuser')
    })

    it.each([
      [{ password: 'password123', role: 'admin' }, 'identifier required'],
      [{ identifier: 'testuser', password: 'password123', role: 'invalid' }, 'role must be admin, leader, or member'],
    ])('rejects invalid input: %j', (input, errorMsg) => {
      const result = validateUser(input)
      expect(result.valid).toBe(false)
      expect(result.errors).toContain(errorMsg)
    })
  })

  describe('validateUpdateUser', () => {
    it('validates partial updates', () => {
      const result = validateUpdateUser({ role: 'leader' })
      expect(result.valid).toBe(true)
      expect(result.data.role).toBe('leader')
    })

    it('rejects empty identifier', () => {
      const result = validateUpdateUser({ identifier: '' })
      expect(result.valid).toBe(false)
      expect(result.errors).toContain('identifier cannot be empty')
    })

    it.each([
      [{ password: '' }, undefined],
      [{ password: undefined }, undefined],
      [{ password: 'newpassword' }, 'newpassword'],
    ])('handles password: %j -> %s', (input, expected) => {
      const result = validateUpdateUser(input)
      expect(result.valid).toBe(true)
      expect(result.data.password).toBe(expected)
    })

    it.each([
      ['role'],
      ['identifier'],
      ['password'],
    ])('handles %s undefined', (field) => {
      const result = validateUpdateUser({})
      expect(result.valid).toBe(true)
      expect(result.data[field]).toBeUndefined()
    })
  })

  describe('validatePoi', () => {
    it('validates valid POI data', () => {
      const result = validatePoi({
        lat: 40.7128,
        lng: -74.0060,
        label: 'Test POI',
        iconType: 'flag',
      })
      expect(result.valid).toBe(true)
      expect(result.data).toMatchObject({
        lat: 40.7128,
        lng: -74.0060,
        label: 'Test POI',
        icon_type: 'flag',
      })
    })

    it('rejects invalid coordinates', () => {
      const result = validatePoi({ lat: 'invalid', lng: -74.0060 })
      expect(result.valid).toBe(false)
      expect(result.errors).toContain('lat and lng required as finite numbers')
    })

    it.each([
      [{ lat: 40.7128, lng: -74.0060 }, 'pin'],
      [{ lat: 40.7128, lng: -74.0060, iconType: 'invalid' }, 'pin'],
    ])('defaults iconType to pin: %j -> %s', (input, expected) => {
      const result = validatePoi(input)
      expect(result.valid).toBe(true)
      expect(result.data.icon_type).toBe(expected)
    })
  })

  describe('validateUpdatePoi', () => {
    it('validates partial updates', () => {
      const result = validateUpdatePoi({ label: 'Updated', lat: 40.7128 })
      expect(result.valid).toBe(true)
      expect(result.data).toMatchObject({ label: 'Updated', lat: 40.7128 })
    })

    it('allows empty updates', () => {
      const result = validateUpdatePoi({})
      expect(result.valid).toBe(true)
      expect(Object.keys(result.data).length).toBe(0)
    })

    it.each([
      [{ iconType: 'invalid' }, 'Invalid iconType'],
      [{ lat: 'invalid' }, 'lat must be a finite number'],
      [{ lng: 'invalid' }, 'lng must be a finite number'],
    ])('rejects invalid input: %j', (input, errorMsg) => {
      const result = validateUpdatePoi(input)
      expect(result.valid).toBe(false)
      expect(result.errors).toContain(errorMsg)
    })

    it('handles all field types', () => {
      const result = validateUpdatePoi({
        label: 'Updated',
        iconType: 'waypoint',
        lat: 41.7128,
        lng: -75.0060,
      })
      expect(result.valid).toBe(true)
      expect(result.data).toMatchObject({
        label: 'Updated',
        icon_type: 'waypoint',
        lat: 41.7128,
        lng: -75.0060,
      })
    })

    it.each([
      ['label'],
      ['icon_type'],
      ['lat'],
      ['lng'],
    ])('handles %s undefined', (field) => {
      const result = validateUpdatePoi({})
      expect(result.valid).toBe(true)
      expect(result.data[field]).toBeUndefined()
    })
  })
})
