const {spawn} = require('child_process')
const test = require('ava')
const {runTest} = require('./util')

process.chdir(__dirname)

const SERVER_PROCESS_DELAY = 100 // ms

let port = 8000
const TESTS = new Map()
  .set('basic', {
    port: port++,
    start(sockets) {
      sockets.connect('alice')
    },
    expected: new Map()
      .set('alice', [
        {
          message: {type: 'users', users: ['alice']},
          action(sockets) {
            sockets.connect('bob')
          }
        },
        {
          message: {type: 'new-user', user: 'bob'},
          action(sockets) {
            sockets.send('alice', {
              type: 'message',
              message: {id: '1', user: 'bob', content: 'hi'}
            })
          }
        },
        {message: {type: 'read', id: '1'}},
        {
          message: {type: 'message', message: {id: '2', user: 'bob', content: 'hi back'}},
          action(sockets) {
            sockets.send('alice', {type: 'read', id: '2'})
          }
        }
      ])
      .set('bob', [
        {message: {type: 'users', users: ['alice', 'bob']}},
        {
          message: {type: 'message', message: {id: '1', user: 'alice', content: 'hi'}},
          action(sockets) {
            sockets.send('bob', {type: 'read', id: '1'})
            sockets.send('bob', {
              type: 'message',
              message: {id: '2', user: 'alice', content: 'hi back'}
            })
          }
        },
        {message: {type: 'read', id: '2'}}
      ])
  })
  .set('logout', {
    port: port++,
    start(sockets) {
      sockets.connect('alice')
    },
    expected: new Map()
      .set('alice', [
        {
          message: {type: 'users', users: ['alice']},
          action(sockets) {
            sockets.connect('bob')
          }
        },
        {message: {type: 'new-user', user: 'bob'}},
        {
          message: {
            type: 'message',
            message: {id: 'while-online', user: 'bob', content: 'lorem'}
          },
          action(sockets) {
            sockets.disconnect('alice')
            setTimeout(() => {
              sockets.send('bob', {
                type: 'message',
                message: {id: 'while-offline', user: 'alice', content: 'ipsum'}
              })
              setTimeout(() => sockets.connect('alice'), 100)
            }, 100)
          }
        },
        // When alice logs in again, she should receive users and the 2 unread messages
        {message: {type: 'users', users: ['alice', 'bob']}},
        {
          message: {
            type: 'message',
            message: {id: 'while-online', user: 'bob', content: 'lorem'}
          }
        },
        {
          message: {
            type: 'message',
            message: {id: 'while-offline', user: 'bob', content: 'ipsum'}
          },
          action(sockets) {
            sockets.send('alice', {type: 'read', id: 'while-offline'})
            sockets.disconnect('alice')
          }
        },
        // When alice logs in again, she should receive users and the 2 unread messages
        {message: {type: 'users', users: ['alice', 'bob']}},
        {
          message: {
            type: 'message',
            message: {id: 'while-online', user: 'bob', content: 'lorem'}
          }
        },
        {
          message: {
            type: 'message',
            message: {id: 'while-offline2', user: 'bob', content: 'dolor'}
          },
          action(sockets) {
            sockets.send('alice', {type: 'read', id: 'while-offline2'})
            sockets.send('alice', {type: 'read', id: 'while-online'})
            sockets.disconnect('alice')
            setTimeout(() => {
              sockets.connect('alice')
              sockets.connect('bob')
            }, 100)
          }
        },
        // When alice logs in again, she should have no unread messages
        {message: {type: 'users', users: ['alice', 'bob']}}
      ])
      .set('bob', [
        {
          message: {type: 'users', users: ['alice', 'bob']},
          action(sockets) {
            sockets.send('bob', {
              type: 'message',
              message: {id: 'while-online', user: 'alice', content: 'lorem'}
            })
          }
        },
        {
          message: {type: 'read', id: 'while-offline'},
          action(sockets) {
            sockets.send('bob', {
              type: 'message',
              message: {id: 'while-offline2', user: 'alice', content: 'dolor'}
            })
            sockets.disconnect('bob')
            setTimeout(() => sockets.connect('alice'), 100)
          }
        },
        // When bob logs in again, he should not receive any read receipts
        {message: {type: 'users', users: ['alice', 'bob']}}
      ])
  })
  .set('send to self', {
    port: port++,
    start(sockets) {
      sockets.connect('eve')
    },
    expected: new Map()
      .set('chuck', [
        {
          message: {type: 'users', users: ['chuck', 'dan', 'eve']},
          action(sockets) {
            sockets.send('chuck', {
              type: 'message',
              message: {id: 'kcuhc1', user: 'chuck', content: 'fc1c74082ec69f1cdb463e7b9c6319e5'}
            })
            sockets.send('chuck', {
              type: 'message',
              message: {id: 'kcuhc2', user: 'chuck', content: '457194698dcb4a719a6894dd935a7b5a'}
            })
            sockets.send('chuck', {
              type: 'message',
              message: {id: 'kcuhc3', user: 'chuck', content: '71eb2a9ff97c0a45bfd8527c26daf07f'}
            })
            sockets.connect('dan')
          }
        },
        {message: {
          type: 'message',
          message: {id: 'kcuhc1', user: 'chuck', content: 'fc1c74082ec69f1cdb463e7b9c6319e5'}
        }},
        {
          message: {
            type: 'message',
            message: {id: 'kcuhc2', user: 'chuck', content: '457194698dcb4a719a6894dd935a7b5a'}
          },
          action(sockets) {
            sockets.send('chuck', {type: 'read', id: 'kcuhc2'})
          }
        },
        {
          message: {
            type: 'message',
            message: {id: 'kcuhc3', user: 'chuck', content: '71eb2a9ff97c0a45bfd8527c26daf07f'}
          }
        },
        {
          message: {type: 'read', id: 'kcuhc2'},
          action(sockets) {
            sockets.disconnect('chuck')
            setTimeout(() => sockets.connect('chuck'), 100)
          }
        },
        // Chuck logs in again; 2 unread messages
        {message: {type: 'users', users: ['chuck', 'dan', 'eve']}},
        {message: {
          type: 'message',
          message: {id: 'kcuhc1', user: 'chuck', content: 'fc1c74082ec69f1cdb463e7b9c6319e5'}
        }},
        {message: {
          type: 'message',
          message: {id: 'kcuhc3', user: 'chuck', content: '71eb2a9ff97c0a45bfd8527c26daf07f'}
        }}
      ])
      .set('dan', [
        {
          message: {type: 'users', users: ['dan', 'eve']},
          action(sockets) {
            sockets.send('dan', {
              type: 'message',
              message: {id: 'nad', user: 'dan', content: '8abc6f71ba4546ed578bf11808ebf7d6'}
            })
          }
        },
        {
          message: {
            type: 'message',
            message: {id: 'nad', user: 'dan', content: '8abc6f71ba4546ed578bf11808ebf7d6'}
          },
          action(sockets) {
            sockets.disconnect('dan')
            setTimeout(() => {
              sockets.connect('eve')
              setTimeout(() => sockets.connect('chuck'), 100)
            }, 100)
          }
        },
        // Dan logs in again; 1 unread message
        {message: {type: 'users', users: ['chuck', 'dan', 'eve']}},
        {
          message: {
            type: 'message',
            message: {id: 'nad', user: 'dan', content: '8abc6f71ba4546ed578bf11808ebf7d6'}
          },
          action(sockets) {
            sockets.disconnect('dan')
            setTimeout(() => sockets.connect('dan'), 100)
          }
        },
        // Dan logs in again; still 1 unread message
        {message: {type: 'users', users: ['chuck', 'dan', 'eve']}},
        {
          message: {
            type: 'message',
            message: {id: 'nad', user: 'dan', content: '8abc6f71ba4546ed578bf11808ebf7d6'}
          },
          action(sockets) {
            sockets.send('dan', {type: 'read', id: 'nad'})
          }
        },
        {
          message: {type: 'read', id: 'nad'},
          action(sockets) {
            sockets.disconnect('dan')
            setTimeout(() => sockets.connect('dan'), 100)
          }
        },
        // Dan logs in again; no unread messages
        {message: {type: 'users', users: ['chuck', 'dan', 'eve']}}
      ])
      .set('eve', [
        {
          message: {type: 'users', users: ['eve']},
          action(sockets) {
            sockets.connect('dan')
          }
        },
        {
          message: {type: 'new-user', user: 'dan'},
          action(sockets) {
            sockets.send('eve', {
              type: 'message',
              message: {id: 'eve', user: 'eve', content: '2a43e533e43879c4bb7b0a027ead3c54'}
            })
          }
        },
        {
          message: {
            type: 'message',
            message: {id: 'eve', user: 'eve', content: '2a43e533e43879c4bb7b0a027ead3c54'}
          },
          action(sockets) {
            sockets.send('eve', {type: 'read', id: 'eve'})
          }
        },
        {
          message: {type: 'read', id: 'eve'},
          action(sockets) {
            sockets.disconnect('eve')
          }
        },
        // Eve logs in again; no unread messages
        {message: {type: 'users', users: ['dan', 'eve']}},
        {
          message: {type: 'new-user', user: 'chuck'},
          action(sockets) {
            sockets.disconnect('eve')
            setTimeout(() => sockets.connect('eve'))
          }
        },
        // Eve logs in again; still no unread messages
        {message: {type: 'users', users: ['chuck', 'dan', 'eve']}},
      ])
  })
  .set('multiple connections', {
    port: port++,
    start(sockets) {
      sockets.connect('faythe')
    },
    expected: new Map()
      .set('faythe', [
        {
          message: {type: 'users', users: ['faythe']},
          action(sockets) {
            sockets.connect('grace')
          }
        },
        {
          message: {type: 'new-user', user: 'grace'},
          action(sockets) {
            sockets.send('faythe', {
              type: 'message',
              message: {id: 'one', user: 'grace', content: 'first'}
            })
          }
        },
        {message: {type: 'read', id: 'one'}},
        {
          message: {
            type: 'message',
            message: {user: 'grace', content: "i'm back", id: 'response'}
          },
          action(sockets) {
            // Change field order to make sure JSON parsing is being used
            sockets.send('faythe', {id: 'response', type: 'read'})
          }
        }
      ])
      .set('grace', [
        {message: {type: 'users', users: ['faythe', 'grace']}},
        {
          message: {
            type: 'message',
            message: {id: 'one', user: 'faythe', content: 'first'}
          },
          action(sockets) {
            sockets.connect('grace')
          }
        },
        // Second connection
        {
          message: {type: 'users', users: ['faythe', 'grace']},
          action(sockets) {
            sockets.send('faythe', {
              type: 'message',
              message: {id: 'two', user: 'grace', content: 'second'}
            })
          }
        },
        {message: {
          type: 'message',
          message: {id: 'one', user: 'faythe', content: 'first'}
        }},
        {
          message: {
            type: 'message',
            message: {id: 'two', user: 'faythe', content: 'second'}
          },
          action(sockets) {
            sockets.connect('grace')
          }
        },
        // Third connection
        {
          message: {type: 'users', users: ['faythe', 'grace']},
          action(sockets) {
            sockets.send('faythe', {
              type: 'message',
              message: {id: 'three', user: 'grace', content: 'third'}
            })
          }
        },
        {message: {
          type: 'message',
          message: {id: 'one', user: 'faythe', content: 'first'}
        }},
        {message: {
          type: 'message',
          message: {id: 'two', user: 'faythe', content: 'second'}
        }},
        {
          message: {
            type: 'message',
            message: {id: 'three', user: 'faythe', content: 'third'}
          },
          action(sockets) {
            sockets.send('grace', {type: 'read', id: 'one'})
            sockets.send('grace', {
              // Change field order to make sure JSON parsing is being used
              message: {user: 'faythe', content: "i'm back", id: 'response'},
              type: 'message'
            })
          }
        },
        {message: {type: 'read', id: 'response'}}
      ])
  })

const servers = new Map()
test.beforeEach.cb(t => {
  const title = t.title.replace('beforeEach hook for ', '')
  const {port} = TESTS.get(title)
  servers.set(title,
    spawn('node', ['../server.js', `${port}`], {stdio: 'inherit'})
      .on('error', t.end)
  )
  setTimeout(t.end, SERVER_PROCESS_DELAY)
})

for (const [name, chatTest] of TESTS) test.cb(name, t => runTest(t, chatTest))

test.afterEach.always.cb(t => {
  const title = t.title.replace('afterEach.always hook for ', '')
  setTimeout(() => {
    servers.get(title).kill()
    t.end()
  }, SERVER_PROCESS_DELAY)
})