'use strict'

const { test } = require('tap')
const { setup, connect, subscribe } = require('./helper')
const aedes = require('../')
const { validateTopic } = require('../lib/utils')

test('validation of `null` topic', function (t) {
  // issue #780
  t.plan(1)
  const err = validateTopic(null, 'SUBSCRIBE')
  t.equal(err.message, 'impossible to SUBSCRIBE to an empty topic')
})

// [MQTT-4.7.1-3]
test('Single-level wildcard should match empty level', function (t) {
  t.plan(4)

  const s = connect(setup())
  t.teardown(s.broker.close.bind(s.broker))

  subscribe(t, s, 'a/+/b', 0, function () {
    s.outStream.once('data', function (packet) {
      t.pass('ok')
    })

    s.inStream.write({
      cmd: 'publish',
      topic: 'a//b',
      payload: 'world'
    })
  })
})

// [MQTT-4.7.3-1]
test('publish empty topic', function (t) {
  t.plan(4)

  const s = connect(setup())

  subscribe(t, s, '#', 0, function () {
    s.outStream.once('data', function (packet) {
      t.fail('no packet')
    })

    s.inStream.write({
      cmd: 'publish',
      topic: '',
      payload: 'world'
    })

    s.broker.close(function () {
      t.equal(s.broker.connectedClients, 0, 'no connected clients')
    })
  })
})

test('publish invalid topic with #', function (t) {
  t.plan(4)

  const s = connect(setup())
  t.teardown(s.broker.close.bind(s.broker))

  subscribe(t, s, '#', 0, function () {
    s.outStream.once('data', function (packet) {
      t.fail('no packet')
    })

    s.inStream.write({
      cmd: 'publish',
      topic: 'hello/#',
      payload: 'world'
    })
  })

  s.broker.on('clientError', function () {
    t.pass('raise an error')
  })
})

test('publish invalid topic with +', function (t) {
  t.plan(4)

  const s = connect(setup())
  t.teardown(s.broker.close.bind(s.broker))

  subscribe(t, s, '#', 0, function () {
    s.outStream.once('data', function (packet) {
      t.fail('no packet')
    })

    s.inStream.write({
      cmd: 'publish',
      topic: 'hello/+/eee',
      payload: 'world'
    })
  })

  s.broker.on('clientError', function () {
    t.pass('raise an error')
  })
})

;['base/#/sub', 'base/#sub', 'base/sub#', 'base/xyz+/sub', 'base/+xyz/sub', ''].forEach(function (topic) {
  test('subscribe to invalid topic with "' + topic + '"', function (t) {
    t.plan(1)

    const s = connect(setup())
    t.teardown(s.broker.close.bind(s.broker))

    s.broker.on('clientError', function () {
      t.pass('raise an error')
    })

    s.inStream.write({
      cmd: 'subscribe',
      messageId: 24,
      subscriptions: [{
        topic,
        qos: 0
      }]
    })
  })

  test('unsubscribe to invalid topic with "' + topic + '"', function (t) {
    t.plan(1)

    const s = connect(setup())
    t.teardown(s.broker.close.bind(s.broker))

    s.broker.on('clientError', function () {
      t.pass('raise an error')
    })

    s.inStream.write({
      cmd: 'unsubscribe',
      messageId: 24,
      unsubscriptions: [topic]
    })
  })
})

test('topics are case-sensitive', function (t) {
  t.plan(4)

  const broker = aedes()
  t.teardown(broker.close.bind(broker))

  const publisher = connect(setup(broker), { clean: true })
  const subscriber = connect(setup(broker), { clean: true })
  const expected = {
    cmd: 'publish',
    topic: 'hello',
    payload: Buffer.from('world'),
    qos: 0,
    dup: false,
    length: 12,
    retain: false
  }

  subscribe(t, subscriber, 'hello', 0, function () {
    subscriber.outStream.on('data', function (packet) {
      t.same(packet, expected, 'packet mush match')
    })
    ;['hello', 'HELLO', 'heLLo', 'HELLO/#', 'hello/+'].forEach(function (topic) {
      publisher.inStream.write({
        cmd: 'publish',
        topic,
        payload: 'world',
        qos: 0,
        retain: false
      })
    })
  })
})

function subscribeMultipleTopics (t, broker, qos, subscriber, subscriptions, done) {
  const publisher = connect(setup(broker))
  subscriber.inStream.write({
    cmd: 'subscribe',
    messageId: 24,
    subscriptions
  })

  subscriber.outStream.once('data', function (packet) {
    t.equal(packet.cmd, 'suback')
    t.same(packet.granted, subscriptions.map(obj => obj.qos))
    t.equal(packet.messageId, 24)

    publisher.inStream.write({
      cmd: 'publish',
      topic: 'hello/world',
      payload: 'world',
      qos,
      messageId: 42
    })

    if (done) {
      done(null, packet)
    }
  })
}

test('Overlapped topics with same QoS', function (t) {
  t.plan(4)

  const broker = aedes()
  t.teardown(broker.close.bind(broker))

  const subscriber = connect(setup(broker))
  const expected = {
    cmd: 'publish',
    topic: 'hello/world',
    payload: Buffer.from('world'),
    qos: 1,
    dup: false,
    length: 20,
    retain: false
  }
  const sub = [
    { topic: 'hello/world', qos: 1 },
    { topic: 'hello/#', qos: 1 }]
  subscribeMultipleTopics(t, broker, 1, subscriber, sub, function () {
    subscriber.outStream.on('data', function (packet) {
      delete packet.messageId
      t.same(packet, expected, 'packet must match')
    })
  })
})

// [MQTT-3.3.5-1]
test('deliver overlapped topics respecting the maximum QoS of all the matching subscriptions - QoS 0 publish', function (t) {
  t.plan(4)

  const broker = aedes()
  t.teardown(broker.close.bind(broker))

  const subscriber = connect(setup(broker))
  const expected = {
    cmd: 'publish',
    topic: 'hello/world',
    payload: Buffer.from('world'),
    qos: 0,
    dup: false,
    length: 18,
    retain: false
  }
  const sub = [
    { topic: 'hello/world', qos: 0 },
    { topic: 'hello/#', qos: 2 }]
  subscribeMultipleTopics(t, broker, 0, subscriber, sub, function () {
    subscriber.outStream.on('data', function (packet) {
      delete packet.messageId
      t.same(packet, expected, 'packet must match')
    })
  })
})

// [MQTT-3.3.5-1]
test('deliver overlapped topics respecting the maximum QoS of all the matching subscriptions - QoS 2 publish', function (t) {
  t.plan(3)

  const broker = aedes()
  t.teardown(broker.close.bind(broker))

  const subscriber = connect(setup(broker))

  const sub = [
    { topic: 'hello/world', qos: 0 },
    { topic: 'hello/#', qos: 2 }]
  subscribeMultipleTopics(t, broker, 2, subscriber, sub, function () {
    subscriber.outStream.on('data', function () {
      t.fail('should receive messages with the maximum QoS')
    })
  })
})

test('Overlapped topics with QoS downgrade', function (t) {
  t.plan(4)

  const broker = aedes()
  t.teardown(broker.close.bind(broker))

  const subscriber = connect(setup(broker))
  const expected = {
    cmd: 'publish',
    topic: 'hello/world',
    payload: Buffer.from('world'),
    qos: 0,
    dup: false,
    length: 18,
    retain: false
  }
  const sub = [
    { topic: 'hello/world', qos: 1 },
    { topic: 'hello/#', qos: 1 }]
  subscribeMultipleTopics(t, broker, 0, subscriber, sub, function () {
    subscriber.outStream.on('data', function (packet) {
      t.same(packet, expected, 'packet must match')
    })
  })
})
