require('../helpers')

should = require('should')
async = require 'async'
primus = require('primus')
commands = require('../../server/commands')
alts = require('../../server/alts')
auth = require('../../server/auth')
{User} = require('../../server/user')
{BattleServer} = require('../../server/server')
{Room} = require('../../server/rooms')
ratings = require('../../server/ratings')
{Factory} = require '../factory'
redis = require('../../server/redis')

generateTeam = ->
  [ Factory("Magikarp")
    Factory("Gyarados")
    Factory('Hitmonchan')
    Factory("Celebi")
    Factory("Blissey")
    Factory("Alakazam") ]

describe "Commands", ->
  beforeEach ->
    @server = new BattleServer()
    @room = new Room()
    @spark1 = @stubSpark()
    @spark2 = @stubSpark()
    @user1 = @server.findOrCreateUser(id: 1, name: "Star Fox", @spark1)
    @user2 = @server.findOrCreateUser(id: 2, name: "Slippy", @spark2)
    @aardvark = @server.findOrCreateUser(id: 3, name: 'aardvark', @stubSpark())
    @bologna = @server.findOrCreateUser(id: 3, name: 'bologna', @stubSpark())
    @offlineName = "husk"
    @room.add(@spark1)
    @room.add(@spark2)
    @emptyRoom = new Room()
    @server.rooms.push(@room)
    @server.join(@spark1)
    @server.join(@spark2)

  describe "#executeCommand", ->
    describe "an invalid command", ->
      it "returns an error to the user", ->
        mock1 = @sandbox.mock(@user1).expects('error').once()
        mock2 = @sandbox.mock(@user2).expects('error').never()
        commands.executeCommand(@server, @user1, @room, "an invalid command")
        mock1.verify()
        mock2.verify()

    describe "rating", ->
      user1Rating = 9001
      user2Rating = 101

      beforeEach (done) ->
        ratings.setRating @user1.name, user1Rating, =>
          ratings.setRating(@user2.name, user2Rating, done)

      it "returns the user's rating to the user without arguments", (done) ->
        spy = @sandbox.spy(@user1, 'announce')
        commands.executeCommand @server, @user1, @room, "rating", =>
          spy.callCount.should.equal(1)
          spy.firstCall.args[2].should.containEql(@user1.name)
          spy.firstCall.args[2].should.containEql(user1Rating)
          done()

      it "returns someone's rating to the user as an argument", (done) ->
        spy = @sandbox.spy(@user1, 'announce')
        commands.executeCommand @server, @user1, @room, "rating", @user2.name, =>
          spy.callCount.should.equal(1)
          spy.callCount.should.equal(1)
          spy.firstCall.args[2].should.containEql(@user2.name)
          spy.firstCall.args[2].should.containEql(user2Rating)
          done()

    describe "voice", ->
      it "returns an error if insufficient authority", (done) ->
        mock1 = @sandbox.mock(@user1).expects('error').once()
        mock2 = @sandbox.mock(@user2).expects('error').never()
        commands.executeCommand @server, @user1, @room, "voice", @user2.name, ->
          mock1.verify()
          mock2.verify()
          done()

      it "voices a user if owner", (done) ->
        @user1.authority = auth.levels.OWNER
        commands.executeCommand @server, @user1, @room, "voice", @user2.name, =>
          @user2.should.have.property("authority")
          @user2.authority.should.equal(auth.levels.DRIVER)
          done()

      it "does not crash if user isn't on yet", (done) ->
        @user1.authority = auth.levels.OWNER
        commands.executeCommand @server, @user1, @room, "voice", @offlineName, =>
          auth.getAuth @offlineName, (err, result) ->
            result.should.equal(auth.levels.DRIVER)
            done()

    describe "mod", ->
      it "returns an error if insufficient authority", (done) ->
        mock1 = @sandbox.mock(@user1).expects('error').once()
        mock2 = @sandbox.mock(@user2).expects('error').never()
        commands.executeCommand @server, @user1, @room, "mod", @user2.name, ->
          mock1.verify()
          mock2.verify()
          done()

      it "mods a user if owner", (done) ->
        @user1.authority = auth.levels.OWNER
        commands.executeCommand @server, @user1, @room, "mod", @user2.name, =>
          @user2.should.have.property("authority")
          @user2.authority.should.equal(auth.levels.MOD)
          done()

      it "does not crash if user isn't on yet", (done) ->
        @user1.authority = auth.levels.OWNER
        commands.executeCommand @server, @user1, @room, "mod", @offlineName, =>
          auth.getAuth @offlineName, (err, result) ->
            result.should.equal(auth.levels.MOD)
            done()

    describe "admin", ->
      it "returns an error if insufficient authority", (done) ->
        mock1 = @sandbox.mock(@user1).expects('error').once()
        mock2 = @sandbox.mock(@user2).expects('error').never()
        commands.executeCommand @server, @user1, @room, "admin", @user2.name, ->
          mock1.verify()
          mock2.verify()
          done()

      it "admins a user if owner", (done) ->
        @user1.authority = auth.levels.OWNER
        commands.executeCommand @server, @user1, @room, "admin", @user2.name, =>
          @user2.should.have.property("authority")
          @user2.authority.should.equal(auth.levels.ADMIN)
          done()

      it "does not crash if user isn't on yet", (done) ->
        @user1.authority = auth.levels.OWNER
        commands.executeCommand @server, @user1, @room, "admin", @offlineName, =>
          auth.getAuth @offlineName, (err, result) ->
            result.should.equal(auth.levels.ADMIN)
            done()

    describe "deauth", ->
      it "returns an error if insufficient authority", (done) ->
        mock1 = @sandbox.mock(@user1).expects('error').once()
        mock2 = @sandbox.mock(@user2).expects('error').never()
        commands.executeCommand @server, @user1, @room, "deauth", @user2.name, ->
          mock1.verify()
          mock2.verify()
          done()

      it "deauthes a user if owner", (done) ->
        @user1.authority = auth.levels.OWNER
        commands.executeCommand @server, @user1, @room, "deauth", @user2.name, =>
          @user2.should.have.property("authority")
          @user2.authority.should.equal(auth.levels.USER)
          done()

      it "does not crash if user isn't on yet", (done) ->
        @user1.authority = auth.levels.OWNER
        commands.executeCommand @server, @user1, @room, "deauth", @offlineName, =>
          auth.getAuth @offlineName, (err, result) ->
            result.should.equal(auth.levels.USER)
            done()

    describe "ban", ->
      it "returns an error if insufficient authority", (done) ->
        mock1 = @sandbox.mock(@user1).expects('error').once()
        mock2 = @sandbox.mock(@spark2).expects('end').never()
        commands.executeCommand @server, @user1, @room, "ban", @user2.name, ->
          mock1.verify()
          mock2.verify()
          done()

      it "bans a user if mod", (done) ->
        mock1 = @sandbox.mock(@user1).expects('error').never()
        mock2 = @sandbox.mock(@spark2).expects('end').once()
        @user1.authority = auth.levels.MOD
        commands.executeCommand @server, @user1, @room, "ban", @user2.name, =>
          mock1.verify()
          mock2.verify()
          auth.getBanTTL @user2.name, (err, ttl) ->
            should.exist(ttl)
            ttl.should.equal(60 * 60)
            done()

      it "bans a user even if user isn't on yet", (done) ->
        mock1 = @sandbox.mock(@user1).expects('error').never()
        @user1.authority = auth.levels.MOD
        commands.executeCommand @server, @user1, @room, "ban", @offlineName, =>
          mock1.verify()
          auth.getBanTTL @offlineName, (err, ttl) ->
            should.exist(ttl)
            ttl.should.equal(60 * 60)
            done()

      it "bans a user for a specified amount of time", (done) ->
        mock = @sandbox.mock(@user1).expects('error').never()
        @user1.authority = auth.levels.MOD
        commands.executeCommand @server, @user1, @room, "ban", @user2.name, "23h", =>
          mock.verify()
          auth.getBanTTL @user2.name, (err, ttl) ->
            should.exist(ttl)
            ttl.should.equal(23 * 60 * 60)
            done()

      it "defaults to an hour if banning for zero minutes", (done) ->
        mock = @sandbox.mock(@user1).expects('error').never()
        @user1.authority = auth.levels.MOD
        commands.executeCommand @server, @user1, @room, "ban", @user2.name, "0", =>
          mock.verify()
          auth.getBanTTL @user2.name, (err, ttl) ->
            should.exist(ttl)
            ttl.should.equal(60 * 60)
            done()

      it "cannot ban over one day if mod", (done) ->
        mock = @sandbox.mock(@user1).expects('error').never()
        @user1.authority = auth.levels.MOD
        commands.executeCommand @server, @user1, @room, "ban", @user2.name, "1y", =>
          mock.verify()
          auth.getBanTTL @user2.name, (err, ttl) ->
            should.exist(ttl)
            ttl.should.equal(1 * 24 * 60 * 60)
            done()

    describe "unban", ->
      it "returns an error if insufficient authority", (done) ->
        mock1 = @sandbox.mock(@user1).expects('error').once()
        mock2 = @sandbox.mock(@user2).expects('error').never()
        commands.executeCommand @server, @user1, @room, "unban", @user2.name, =>
          mock1.verify()
          mock2.verify()
          done()

      it "unbans a user if mod", (done) ->
        @user1.authority = auth.levels.MOD
        commands.executeCommand @server, @user1, @room, "ban", @user2.name, =>
            commands.executeCommand @server, @user1, @room, "unban", @user2.name, =>
              auth.getBanTTL @user2.name, (err, ttl) ->
                should.exist(ttl)
                ttl.should.equal(-2)
                done()

      it "returns an error if user is not banned", (done) ->
        mock1 = @sandbox.mock(@user1).expects('error').once()
        mock2 = @sandbox.mock(@user2).expects('error').never()
        @user1.authority = auth.levels.MOD
        commands.executeCommand @server, @user1, @room, "unban", @user2.name, =>
          mock1.verify()
          mock2.verify()
          done()

      it "unbans a user even if user isn't on yet", (done) ->
        mock = @sandbox.mock(@user1).expects('error').never()
        @user1.authority = auth.levels.MOD
        commands.executeCommand @server, @user1, @room, "ban", @offlineName, =>
          commands.executeCommand @server, @user1, @room, "unban", @offlineName, =>
            mock.verify()
            auth.getBanTTL @user2.name, (err, ttl) ->
              should.exist(ttl)
              ttl.should.equal(-2)
              done()

    describe "mute", ->
      it "returns an error if insufficient authority", (done) ->
        mock1 = @sandbox.mock(@user1).expects('error').once()
        mock2 = @sandbox.mock(@user2).expects('error').never()
        commands.executeCommand @server, @user1, @room, "mute", @user2.name, ->
          mock1.verify()
          mock2.verify()
          done()

      it "mutes a user if mod", (done) ->
        mock = @sandbox.mock(@user1).expects('error').never()
        @user1.authority = auth.levels.MOD
        commands.executeCommand @server, @user1, @room, "mute", @user2.name, =>
          mock.verify()
          auth.getMuteTTL @user2.name, (err, ttl) ->
            should.exist(ttl)
            ttl.should.equal(10 * 60)
            done()

      it "mutes a user even if user isn't on yet", (done) ->
        mock = @sandbox.mock(@user1).expects('error').never()
        @user1.authority = auth.levels.MOD
        commands.executeCommand @server, @user1, @room, "mute", @offlineName, =>
          mock.verify()
          auth.getMuteTTL @offlineName, (err, ttl) ->
            should.exist(ttl)
            ttl.should.equal(10 * 60)
            done()

      it "mutes a user for a specified amount of time", (done) ->
        mock = @sandbox.mock(@user1).expects('error').never()
        @user1.authority = auth.levels.MOD
        commands.executeCommand @server, @user1, @room, "mute", @user2.name, "23h", =>
          mock.verify()
          auth.getMuteTTL @user2.name, (err, ttl) ->
            should.exist(ttl)
            ttl.should.equal(23 * 60 * 60)
            done()

      it "defaults to 10 if muting for zero minutes", (done) ->
        mock = @sandbox.mock(@user1).expects('error').never()
        @user1.authority = auth.levels.MOD
        commands.executeCommand @server, @user1, @room, "mute", @user2.name, "0", =>
          mock.verify()
          auth.getMuteTTL @user2.name, (err, ttl) ->
            should.exist(ttl)
            ttl.should.equal(10 * 60)
            done()

      it "cannot mute over two days if mod", (done) ->
        mock = @sandbox.mock(@user1).expects('error').never()
        @user1.authority = auth.levels.MOD
        commands.executeCommand @server, @user1, @room, "mute", @user2.name, "1y", =>
          mock.verify()
          auth.getMuteTTL @user2.name, (err, ttl) ->
            should.exist(ttl)
            ttl.should.equal(2 * 24 * 60 * 60)
            done()

    describe "unmute", ->
      it "returns an error if insufficient authority", (done) ->
        mock1 = @sandbox.mock(@user1).expects('error').once()
        mock2 = @sandbox.mock(@user2).expects('error').never()
        commands.executeCommand @server, @user1, @room, "unmute", @user2.name, =>
          mock1.verify()
          mock2.verify()
          done()

      it "unmutes a user if mod", (done) ->
        @user1.authority = auth.levels.MOD
        commands.executeCommand @server, @user1, @room, "mute", @user2.name, =>
            commands.executeCommand @server, @user1, @room, "unmute", @user2.name, =>
              auth.getMuteTTL @user2.name, (err, ttl) ->
                should.exist(ttl)
                ttl.should.equal(-2)
                done()

      it "returns an error if user is not muted", (done) ->
        mock1 = @sandbox.mock(@user1).expects('error').once()
        mock2 = @sandbox.mock(@user2).expects('error').never()
        @user1.authority = auth.levels.MOD
        commands.executeCommand @server, @user1, @room, "unmute", @user2.name, =>
          mock1.verify()
          mock2.verify()
          done()

      it "unmutes a user even if user isn't on yet", (done) ->
        mock1 = @sandbox.mock(@user1).expects('error').never()
        @user1.authority = auth.levels.MOD
        commands.executeCommand @server, @user1, @room, "mute", @offlineName, =>
          commands.executeCommand @server, @user1, @room, "unmute", @offlineName, =>
            mock1.verify()
            auth.getMuteTTL @user2.name, (err, ttl) ->
              should.exist(ttl)
              ttl.should.equal(-2)
              done()

    describe "battles", ->
      it "returns an error if no user is passed", (done) ->
        mock1 = @sandbox.mock(@user1).expects('error').once()
        mock2 = @sandbox.mock(@user2).expects('error').never()
        commands.executeCommand @server, @user1, @room, "battles", ->
          mock1.verify()
          mock2.verify()
          done()

      it "returns all battles that user is in if user is passed", (done) ->
        @server.queuePlayer(@user1.name, generateTeam()).should.be.empty
        @server.queuePlayer(@user2.name, generateTeam()).should.be.empty
        @server.queuePlayer(@aardvark.name, generateTeam()).should.be.empty
        @server.queuePlayer(@bologna.name, generateTeam()).should.be.empty
        @server.beginBattles (err, battleIds) =>
          if err then throw err
          battleIds.length.should.equal(2)
          spy = @sandbox.spy(@user1, 'announce')
          commands.executeCommand @server, @user1, @room, "battles", @user2.name, =>
            spy.callCount.should.equal(1)
            spy.firstCall.args[2].should.containEql(@user2.name)
            spy.firstCall.args[2].should.containEql(battleIds[0])
            spy.firstCall.args[2].should.not.containEql(@user1.name)
            spy.firstCall.args[2].should.not.containEql(battleIds[1])
            done()

      it "does not containEql alts in the battle list", (done) ->
        @server.queuePlayer(@user1.name, [ Factory("Magikarp") ])
        @server.queuePlayer(@user2.name, [ Factory("Magikarp") ], "Im an Alt")
        @server.beginBattles (err, battleIds) =>
          if err then throw err
          spy = @sandbox.spy(@user1, 'announce')
          commands.executeCommand @server, @user1, @room, "battles", @user2.name, =>
            if err then throw err
            spy.callCount.should.equal(1)
            spy.firstCall.args[2].should.containEql(@user2.name)
            done()

    describe "topic", ->
      it "returns an error if insufficient authority", (done) ->
        mock1 = @sandbox.mock(@user1).expects('error').once()
        commands.executeCommand @server, @user1, @room, "topic", "test topic", ->
          mock1.verify()
          done()

      it "updates the channel topic", (done) ->
        topicName = "a test"
        @user1.authority = auth.levels.ADMIN
        mock = @sandbox.mock(@room).expects("setTopic").withArgs(topicName).once()
        commands.executeCommand @server, @user1, @room, "topic", topicName, ->
          mock.verify()
          done()

    describe "wall", ->
      it "returns an error if insufficient authority", (done) ->
        mock1 = @sandbox.mock(@user1).expects('error').once()
        commands.executeCommand @server, @user1, @room, "wall", "hi", ->
          mock1.verify()
          done()

      it "messages all rooms and all battles", (done) ->
        mock = @sandbox.mock(@room).expects('announce').once()
        spy1 = @sandbox.spy(@spark1, 'send')
        spy2 = @sandbox.spy(@spark2, 'send')
        @server.queuePlayer(@user1.name, generateTeam()).should.be.empty
        @server.queuePlayer(@user2.name, generateTeam()).should.be.empty
        @server.beginBattles (err, battleIds) =>
          if err then throw err
          @user1.authority = auth.levels.ADMIN
          commands.executeCommand @server, @user1, @room, "wall", "derper", =>
            mock.verify()
            spy1.calledWithMatch("announce", battleIds[0], 'warning', "derper").should.be.true
            spy2.calledWithMatch("announce", battleIds[0], 'warning', "derper").should.be.true
            done()

    describe "lockdown", ->
      it "returns an error if insufficient authority", (done) ->
        mock = @sandbox.mock(@user1).expects('error').once()
        commands.executeCommand @server, @user1, @room, "lockdown", ->
          mock.verify()
          done()

      it "stops battles from occuring", (done) ->
        mock = @sandbox.mock(@server).expects('lockdown').once()
        @user1.authority = auth.levels.OWNER
        commands.executeCommand @server, @user1, @room, "lockdown", ->
          mock.verify()
          done()

      it "can start them again", (done) ->
        mock = @sandbox.mock(@server).expects('unlockdown').once()
        @user1.authority = auth.levels.OWNER
        commands.executeCommand @server, @user1, @room, "lockdown", "off", ->
          mock.verify()
          done()

    describe "whois", ->
      beforeEach (done) ->
        async.parallel([
          alts.createAlt.bind(alts, @user1.name, 'alt1')
          alts.createAlt.bind(alts, @user1.name, 'alt2')
        ], done)

      it "returns an error if insufficient authority", (done) ->
        mock = @sandbox.mock(@user1).expects('error').once()
        commands.executeCommand @server, @user1, @room, "whois", @user1.name, =>
          mock.verify()
          done()

      it "returns a list of alts and the main account", (done) ->
        @user1.authority = auth.levels.MOD
        spy = @sandbox.spy(@user1, 'announce')
        commands.executeCommand @server, @user1, @room, "whois", @user1.name, =>
          spy.callCount.should.equal(1)
          spy.firstCall.args[2].should.containEql(@user1.name)
          spy.firstCall.args[2].should.containEql("alt1")
          spy.firstCall.args[2].should.containEql("alt2")
          done()
