import { describe, it, expect } from 'vitest'
import { parseTakStreamFrame, parseTraditionalXmlFrame, parseCotPayload } from '../../../server/utils/cotParser.js'

const encodeVarint = (value, bytes = []) => {
  const byte = value & 0x7F
  const remaining = value >>> 7
  if (remaining === 0) {
    return [...bytes, byte]
  }
  return encodeVarint(remaining, [...bytes, byte | 0x80])
}

function buildTakFrame(payload) {
  const buf = Buffer.from(payload, 'utf8')
  const varint = encodeVarint(buf.length)
  return Buffer.concat([Buffer.from([0xBF]), Buffer.from(varint), buf])
}

describe('cotParser', () => {
  describe('parseTakStreamFrame', () => {
    it('parses valid frame', () => {
      const payload = '<event uid="x" type="a-f-G"><point lat="1" lon="2"/></event>'
      const frame = buildTakFrame(payload)
      const result = parseTakStreamFrame(frame)
      expect(result).not.toBeNull()
      expect(result.payload.toString('utf8')).toBe(payload)
      expect(result.bytesConsumed).toBe(frame.length)
    })

    it('returns null for incomplete buffer', () => {
      const frame = buildTakFrame('<e/>')
      const partial = frame.subarray(0, 2)
      expect(parseTakStreamFrame(partial)).toBeNull()
    })

    it('returns null for wrong magic', () => {
      const payload = '<e/>'
      const buf = Buffer.concat([Buffer.from([0x00]), Buffer.from([payload.length]), Buffer.from(payload)])
      expect(parseTakStreamFrame(buf)).toBeNull()
    })

    it('returns null for payload length exceeding max', () => {
      const hugeLen = 64 * 1024 + 1
      const varint = encodeVarint(hugeLen)
      const buf = Buffer.concat([Buffer.from([0xBF]), Buffer.from(varint)])
      expect(parseTakStreamFrame(buf)).toBeNull()
    })
  })

  describe('parseTraditionalXmlFrame', () => {
    it('parses one XML message delimited by </event>', () => {
      const xml = '<event uid="x" type="a-f-G"><point lat="1" lon="2"/></event>'
      const buf = Buffer.from(xml, 'utf8')
      const result = parseTraditionalXmlFrame(buf)
      expect(result).not.toBeNull()
      expect(result.payload.toString('utf8')).toBe(xml)
      expect(result.bytesConsumed).toBe(buf.length)
    })

    it('returns null when buffer does not start with <', () => {
      expect(parseTraditionalXmlFrame(Buffer.from('x<event></event>'))).toBeNull()
      expect(parseTraditionalXmlFrame(Buffer.from([0xBF, 0x00]))).toBeNull()
    })

    it('returns null when </event> not yet received', () => {
      const partial = Buffer.from('<event uid="x"><point lat="1" lon="2"/>', 'utf8')
      expect(parseTraditionalXmlFrame(partial)).toBeNull()
    })

    it('extracted payload parses as auth CoT', () => {
      const xml = '<event><detail><auth username="itak" password="mypass"/></detail></event>'
      const buf = Buffer.from(xml, 'utf8')
      const result = parseTraditionalXmlFrame(buf)
      expect(result).not.toBeNull()
      const parsed = parseCotPayload(result.payload)
      expect(parsed).not.toBeNull()
      expect(parsed.type).toBe('auth')
      expect(parsed.username).toBe('itak')
      expect(parsed.password).toBe('mypass')
    })
  })

  describe('parseCotPayload', () => {
    it('parses position CoT XML', () => {
      const xml = '<event uid="device-1" type="a-f-G-U-C"><point lat="37.7" lon="-122.4"/><detail><contact callsign="Bravo"/></detail></event>'
      const result = parseCotPayload(Buffer.from(xml, 'utf8'))
      expect(result).not.toBeNull()
      expect(result.type).toBe('cot')
      expect(result.id).toBe('device-1')
      expect(result.lat).toBe(37.7)
      expect(result.lng).toBe(-122.4)
      expect(result.label).toBe('Bravo')
    })

    it('parses auth CoT with detail.auth', () => {
      const xml = '<event><detail><auth username="user1" password="secret123"/></detail></event>'
      const result = parseCotPayload(Buffer.from(xml, 'utf8'))
      expect(result).not.toBeNull()
      expect(result.type).toBe('auth')
      expect(result.username).toBe('user1')
      expect(result.password).toBe('secret123')
    })

    it('parses auth CoT with __auth', () => {
      const xml = '<event><detail><__auth username="u2" password="p2"/></detail></event>'
      const result = parseCotPayload(Buffer.from(xml, 'utf8'))
      expect(result).not.toBeNull()
      expect(result.type).toBe('auth')
      expect(result.username).toBe('u2')
      expect(result.password).toBe('p2')
    })

    it('returns null for auth with empty username', () => {
      const xml = '<event><detail><auth username="   " password="p"/></detail></event>'
      const result = parseCotPayload(Buffer.from(xml, 'utf8'))
      expect(result).toBeNull()
    })

    it('parses position with point.lat and point.lon (no @_ prefix)', () => {
      const xml = '<event uid="x" type="a-f-G"><point lat="5" lon="10"/></event>'
      const result = parseCotPayload(Buffer.from(xml, 'utf8'))
      expect(result).not.toBeNull()
      expect(result.lat).toBe(5)
      expect(result.lng).toBe(10)
    })

    it('returns null for non-XML payload', () => {
      expect(parseCotPayload(Buffer.from('not xml'))).toBeNull()
    })

    it('uses uid as label when no contact/callsign', () => {
      const xml = '<event uid="device-99" type="a-f-G"><point lat="1" lon="2"/></event>'
      const result = parseCotPayload(Buffer.from(xml, 'utf8'))
      expect(result).not.toBeNull()
      expect(result.type).toBe('cot')
      expect(result.label).toBe('device-99')
    })

    it('uses point inside event when not at root', () => {
      const xml = '<event uid="x" type="a-f-G"><point lat="10" lon="20"/></event>'
      const result = parseCotPayload(Buffer.from(xml, 'utf8'))
      expect(result).not.toBeNull()
      expect(result.lat).toBe(10)
      expect(result.lng).toBe(20)
    })
  })
})
