import {assert} from '@webex/test-helper-chai';
import {skipInNode} from '@webex/test-helper-mocha';

import testUtils from './testUtils';


require('dotenv').config();

const webexTestUsers = require('./webexTestUsers');

let userSet, alice, bob, chris;
// TODO: Merge all the test it sections into one

skipInNode(describe)('plugin-meeting', () => {
  before(() => webexTestUsers.generateTestUsers({count: 3})
    .then((users) => {
      userSet = users;
      alice = userSet[0];
      bob = userSet[1];
      chris = userSet[2];
      alice.name = 'alice';
      bob.name = 'bob';
      chris.name = 'chris';
      alice.webex.meetings.name = 'alice';
      bob.webex.meetings.name = 'bob';
      chris.webex.meetings.name = 'chris';
    })
    .then(() => Promise.all([testUtils.syncAndEndMeeting(alice),
      testUtils.syncAndEndMeeting(bob)]))
    .catch((error) => {
      console.log(error);
    }));

  after(() => {
    const promise = [];

    userSet.forEach((user) => {
      promise.push(user.webex.internal.mercury.disconnect());
    });

    return Promise.all(promise)
      .then(() => webexTestUsers.remove(userSet));
    // TODO: end is not a function on browser object
  });

  // Alice calls bob and bob rejects it
  describe('End outgoing Call', () => {
    after(() => {
      alice.meeting = null;
      bob.meeting = null;
    });

    it('Alice Ends a outgoing meeting', () => Promise.all([
      testUtils.delayedPromise(alice.webex.meetings.create(bob.emailAddress)),
      testUtils.waitForEvents([{scope: alice.webex.meetings, event: 'meeting:added', user: alice}])
    ])
      .then(() => Promise.all([
        testUtils.delayedPromise(alice.meeting.join()),
        testUtils.waitForEvents([{scope: bob.webex.meetings, event: 'meeting:added', user: bob}])
      ]))
      .then(() => {
        // bob and alice have meeting object
        bob.meeting.acknowledge('INCOMING');
        assert.equal(bob.meeting.sipUri, alice.emailAddress);
        assert.equal(alice.meeting.sipUri, bob.emailAddress);
        assert.equal(bob.meeting.state, 'IDLE');
        assert.equal(alice.meeting.state, 'JOINED');
      })
      .then(function aliceLeavesMeetingAndBobGetsMeetingRemoved() {
        return Promise.all([
          testUtils.delayedPromise(alice.meeting.leave()),
          testUtils.waitForEvents([{scope: bob.webex.meetings, event: 'meeting:removed', user: bob}])
        ]);
      })
      .then(() => testUtils.waitForStateChange(alice.meeting, 'LEFT'))
      .then(() => Promise.all([
        testUtils.waitForCallEnded(alice, bob.emailAddress),
        testUtils.waitForCallEnded(bob, alice.emailAddress)
      ]))
      .then(() => {
        assert.equal(alice.webex.meetings.getMeetingByType('sipUri', bob.emailAddress), null);
        assert.equal(bob.webex.meetings.getMeetingByType('sipUri', alice.emailAddress), null);
      })
      .catch((err) => {
        console.log('ERROR JOIN ', err);
        throw err;
      }));
  });

  // The event was coming but incomplete
  // 1) Test user doesnt have locus tag information

  // Alice calls bob and bob rejects it
  describe('reject Incoming Call', () => {
    it('alice dials bob and bob receives meeting added', () => Promise.all([
      testUtils.delayedPromise(alice.webex.meetings.create(bob.emailAddress)),
      testUtils.waitForEvents([{scope: alice.webex.meetings, event: 'meeting:added', user: alice}])
    ])
      .then(() => Promise.all([
        testUtils.delayedPromise(alice.meeting.join()),
        testUtils.waitForEvents([{scope: bob.webex.meetings, event: 'meeting:added', user: bob}])
      ]))
      .then(function alicebobJoined() {
        assert.exists(bob.meeting);
        assert.exists(alice.meeting);
        assert.equal(bob.meeting.sipUri, alice.emailAddress);
        assert.equal(alice.meeting.sipUri, bob.emailAddress);
        assert.exists(bob.meeting.partner);
        assert.exists(alice.meeting.partner);
      })
      .then(function bobState() {
        testUtils.waitForStateChange(bob.meeting, 'IDLE');
      })
      .then(function aliceState() {
        testUtils.waitForStateChange(alice.meeting, 'JOINED');
      })
      .then(function bobDeclinedCall() {
        return bob.meeting.acknowledge('INCOMING')
          .then(() => bob.meeting.decline('BUSY'))
          .then(() => testUtils.waitForStateChange(bob.meeting, 'DECLINED'))
          .catch((e) => { console.error('Bob decline call not successful', e); throw e; });
      })
      .then(function aliceLeaveMeeting() {
        assert.equal(alice.meeting.state, 'JOINED');

        return alice.meeting.leave()
          .then(() => testUtils.waitForStateChange(alice.meeting, 'LEFT'))
          .then(() => testUtils.waitForStateChange(bob.meeting, 'DECLINED'))
          .catch((e) => { console.error('alice was not able to leave the meeting', e); throw e; });
      })
      .then(function WaitForMeetingEnd() {
        return Promise.all([
          testUtils.waitForCallEnded(alice, bob.emailAddress),
          testUtils.waitForCallEnded(bob, alice.emailAddress)
        ])
          .then(() => {
            assert.equal(alice.webex.meetings.getMeetingByType('sipUri', bob.emailAddress), null);
            assert.equal(bob.webex.meetings.getMeetingByType('sipUri', alice.emailAddress), null);
          })
          .catch((e) => { console.error('Alice bob meeting is deleted', e); throw e; });
      }));
  });

  // Alice calls bob and bob rejects it
  describe('Successful 1:1 meeting (including Guest)', function () {
    this.timeout(80000);
    it('No previous Call', () => {
      assert.equal(Object.keys(bob.webex.meetings.getAllMeetings()), 0);
      assert.equal(Object.keys(alice.webex.meetings.getAllMeetings()), 0);

      return alice.webex.internal.conversation.create({participants: [bob]})
        .then((conversation) => {
          console.log('CONVERSATION CREATED ', JSON.stringify(conversation));

          return alice.webex.internal.conversation.post(conversation, {displayName: 'hello world how are you '});
        });
    });

    it('alice dials bob and adds media', () => Promise.all([
      testUtils.delayedPromise(alice.webex.meetings.create(bob.emailAddress)),
      testUtils.waitForEvents([{scope: alice.webex.meetings, event: 'meeting:added', user: alice}])
    ])
      .then(function aliceJoinsMeeting() {
        return Promise.all([
          testUtils.delayedPromise(alice.meeting.join()),
          testUtils.waitForEvents([{scope: bob.webex.meetings, event: 'meeting:added', user: bob}])
        ]);
      })
      .then(() => assert.equal(bob.meeting.partner.state, 'JOINED'))
      .then(() => testUtils.addMedia(alice)));

    it('bob joins the meeting', () => {
      bob.meeting.acknowledge('INCOMING');

      return Promise.all([
        testUtils.delayedPromise(bob.meeting.join()),
        testUtils.waitForEvents([{scope: alice.meeting.members, event: 'members:update', user: alice}])
          .then((response) => {
            const bobParticipant = response[0].result.delta.updated.find((member) => bob.meeting.members.selfId === member.id);

            assert.equal(bobParticipant.status, 'IN_MEETING');
          })
      ])
        .then(() => testUtils.addMedia(bob))
        .then(() => {
          assert.equal(bob.meeting.sipUri, alice.emailAddress);
          assert.equal(alice.meeting.sipUri, bob.emailAddress);
          assert.exists(alice.meeting.members.locusUrl);
          assert.equal(alice.meeting.type, 'CALL');
          assert.equal(bob.meeting.type, 'CALL');
        })
        .then(function bobState() {
          testUtils.waitForStateChange(bob.meeting, 'JOINED');
        })
        .then(function aliceState() {
          testUtils.waitForStateChange(alice.meeting, 'JOINED');
        })
        .catch((e) => console.log('Error joining one_on_one', e));
    });

    it('check for meeting properties', () => {
      assert.exists(alice.meeting.userId, 'userId not present');
      assert.exists(alice.meeting.deviceUrl, 'deviceUrl not present');
      assert.exists(alice.meeting.partner, 'partner not present');
      assert.exists(alice.meeting.type, 'type not present');
      assert.exists(alice.meeting.state, 'state not present');
      assert.exists(alice.meeting.guest, 'guest not present');
      assert.exists(alice.meeting.mediaProperties, 'mediaProperties not Present');
      assert.exists(alice.meeting.mediaProperties.mediaDirection, 'mediaDirection not present');
      // assert.exists(alice.meeting.joinedWith);
      assert.exists(alice.meeting.members.selfId, 'selfId not present');
    });

    it('check for getStats', () => {
      const options = {
        useConfig: true,
        senders: [
          {
            id: 'audioSend',
            history: true,
            correlate: 'audio', // NECESSARY KEY
            onData: (k) => { console.log('audioSend stats data cb', k); }
          }
        ],
        receivers: [
          {
            id: 'audioRecv',
            history: true,
            correlate: 'audio', // NECESSARY KEY
            onData: (k) => { console.log('audioRecv stats data cb', k); }
          }
        ]
      };

      alice.meeting.getStats(options, true);
      bob.meeting.getStats(options, true);

      assert.exists(alice.meeting.getStats());
      assert.exists(bob.meeting.getStats());

      assert.exists(alice.meeting.getStats().getSender('audioSend'));
      assert.exists(alice.meeting.getStats().getReceiver('audioRecv'));
      assert.exists(bob.meeting.getStats().getSender('audioSend'));
      assert.exists(bob.meeting.getStats().getReceiver('audioRecv'));

      return testUtils.waitUntil(7000);
    });

    it('check populated get stats data', () => {
      assert.ok(alice.meeting.getStats().getSender('audioSend').getHistory().get().length);
      assert.ok(alice.meeting.getStats().getReceiver('audioRecv').getHistory().get().length);
      assert.ok(bob.meeting.getStats().getSender('audioSend').getHistory().get().length);
      assert.ok(bob.meeting.getStats().getReceiver('audioRecv').getHistory().get().length);
    });

    it('analyze getStats data', () => {
      const aliceThirtySecondsData1 = alice.meeting.getStats().getSender('audioSend').getHistory().getSlice(5);
      const analysis1 = alice.webex.meetings.getAnalyzer().analyze(aliceThirtySecondsData1, {analysisKeys: [{key: 'bytesSent', check: 'increasing'}]});
      const aliceThirtySecondsData2 = alice.meeting.getStats().getReceiver('audioRecv').getHistory().getSlice(5);
      const analysis2 = alice.webex.meetings.getAnalyzer().analyze(aliceThirtySecondsData2, {analysisKeys: [{key: 'bytesReceived', check: 'increasing'}]});

      const bobThirtySecondsData3 = bob.meeting.getStats().getSender('audioSend').getHistory().getSlice(5);
      const analysis3 = alice.webex.meetings.getAnalyzer().analyze(bobThirtySecondsData3, {analysisKeys: [{key: 'bytesSent', check: 'increasing'}]});
      const bobThirtySecondsData4 = bob.meeting.getStats().getReceiver('audioRecv').getHistory().getSlice(5);
      const analysis4 = alice.webex.meetings.getAnalyzer().analyze(bobThirtySecondsData4, {analysisKeys: [{key: 'bytesReceived', check: 'increasing'}]});

      assert.equal(analysis1.valid, true);
      assert.equal(analysis2.valid, true);
      assert.equal(analysis3.valid, true);
      assert.equal(analysis4.valid, true);
    });

    it('alice Audio Mute ', () => Promise.all([
      testUtils.delayedPromise(alice.meeting.muteAudio()),
      testUtils.waitForEvents([{scope: bob.meeting.members, event: 'members:update'}])
        .then((response) => {
          const aliceParticipant = response[0].result.delta.updated.find((member) => alice.meeting.members.selfId === member.id);

          console.log('Alice participant', JSON.stringify(aliceParticipant));
          console.log('SELF STATE ', JSON.stringify(alice.meeting.locusInfo.participants));
          assert.equal(aliceParticipant.isAudioMuted, true);
        })
    ])
      .then(() => {
        assert.equal(alice.meeting.audio.muted, true);

        return testUtils.waitUntil(4000);
      }));

    it('alice Audio unMute ', () => Promise.all([
      testUtils.delayedPromise(alice.meeting.unmuteAudio()),
      testUtils.waitForEvents([{scope: bob.meeting.members, event: 'members:update'}])
        .then((response) => {
          const aliceParticipant = response[0].result.delta.updated.find((member) => alice.meeting.members.selfId === member.id);

          assert.equal(aliceParticipant.isAudioMuted, false);
        })
    ])
      .then(() => {
        assert.equal(alice.meeting.audio.muted, false);
      }));

    it('alice Video Mute', () => Promise.all([
      testUtils.delayedPromise(alice.meeting.muteVideo()),
      testUtils.waitForEvents([{scope: alice.meeting.members, event: 'members:update'}])
        .then((response) => {
          const aliceParticipant = response[0].result.delta.updated.find((member) => alice.meeting.members.selfId === member.id);

          assert.equal(aliceParticipant.isVideoMuted, true);
        })
    ])
      .then(() => {
        assert.equal(alice.meeting.video.muted, true);
      }));

    it('alice video unMute', () => Promise.all([
      testUtils.delayedPromise(alice.meeting.unmuteVideo()),
      testUtils.waitForEvents([{scope: bob.meeting.members, event: 'members:update'}])
        .then((response) => {
          const aliceParticipant = response[0].result.delta.updated.find((member) => alice.meeting.members.selfId === member.id);

          assert.equal(aliceParticipant.isVideoMuted, false);
        })
    ])
      .then(() => {
        assert.equal(alice.meeting.video.muted, false);
      }));

    it('alice update Audio', () => alice.meeting.getMediaStreams({sendAudio: true})
      .then((response) => alice.meeting.updateAudio({
        sendAudio: true,
        receiveAudio: true,
        stream: response[0]
      })
        .then(() => {
          console.log('AUDIO ', alice.meeting.mediaProperties.peerConnection.audioTransceiver.sender.track);
          assert.equal(alice.meeting.mediaProperties.audioTrack.id, response[0].getAudioTracks()[0].id);
        })));

    it('alice updadte video', () => alice.meeting.getMediaStreams({sendVideo: true})
      .then((response) => alice.meeting.updateVideo({
        sendVideo: true,
        receiveVideo: true,
        stream: response[0]
      })
        .then(() => {
          assert.equal(alice.meeting.mediaProperties.videoTrack.id, response[0].getVideoTracks()[0].id);
        })));

    it('alice shares the screen', () => alice.meeting.getMediaStreams({
      sendShare: true
    })
      .then((response) => Promise.all([
        alice.meeting.updateShare({
          sendShare: true,
          receiveShare: true,
          stream: response[1]
        }),
        testUtils.waitForEvents([{scope: alice.meeting, event: 'meeting:startedSharingLocal'}]),
        testUtils.waitForEvents([{scope: bob.meeting.members, event: 'members:update'}])
          .then((response) => {
            console.log('SCREEN SHARE RESPONSE ', JSON.stringify(response));
          })
      ]))
      .then(() => {
        assert.equal(alice.meeting.isSharing, true);
        console.log('SCREEN SHARE PARTICIPANTS ', JSON.stringify(alice.meeting.locusInfo.participants));

        return testUtils.waitUntil(4000);
      }));

    it('alice stops sharing ', () => Promise.all([
      testUtils.delayedPromise(alice.meeting.updateShare({
        sendShare: false,
        receiveShare: true
      })),
      testUtils.waitForEvents([{scope: alice.meeting, event: 'meeting:stoppedSharingLocal'}])
    ])
      .then(() => {
        assert.equal(alice.meeting.isSharing, false);
      }));

    it.skip('alice adds chris as guest to 1:1 meeting', () => Promise.all([
      testUtils.delayedPromise(alice.meeting.invite({emailAddress: chris.emailAddress})),
      testUtils.waitForEvents([{scope: chris.webex.meetings, event: 'meeting:added', user: chris}]),
      testUtils.waitForEvents([{scope: alice.meeting.members, event: 'members:update'}])
        .then((response) => {
          const chrisParticipant = response[0].result.delta.added.find((member) => chris.emailAddress === member.email);

          assert.equal(chrisParticipant.status, 'NOT_IN_MEETING');
        })
    ])
      .catch((e) => { console.error('Error adding chris as guest ', e); throw e; })
      .then(function memberUpdated() {
        assert.exists(chris.meeting);

        return Promise.all([
          testUtils.delayedPromise(chris.meeting.join()),
          testUtils.waitForEvents([{scope: alice.meeting.members, event: 'members:update'}])
            .then((response) => {
              const chrisParticipant = response[0].result.delta.updated.find((member) => chris.meeting.members.selfId === member.id);

              assert.equal(chrisParticipant.status, 'IN_LOBBY');

              return alice.meeting.admit(chrisParticipant.id);
            })
        ])
          .then(() => {
            assert.equal(alice.meeting.members.membersCollection.get(chris.meeting.members.selfId).participant.state, 'JOINED');
          })
          .then(() => testUtils.waitForStateChange(chris.meeting, 'JOINED'))
          .then(() => testUtils.addMedia(chris));
      })
      .then(() => Promise.all([
        testUtils.delayedPromise(chris.meeting.leave()),
        testUtils.waitForEvents([{scope: alice.meeting.members, event: 'members:update'}])
          .then((response) => {
            console.log('CHRIS RESPONSE ', response[0]);
            const chrisParticipant = response[0].result.delta.updated.find((member) => chris.meeting.members.selfId === member.id);

            assert.equal(chrisParticipant.status, 'NOT_IN_MEETING');
          })
      ]))
      .catch((e) => { console.error('Error chris joining the meeting ', e); throw e; }));

    it('shut down get stats', () => {
      alice.meeting.stopStats();
      bob.meeting.stopStats();
      assert.ok(!alice.meeting.stats);
      assert.ok(!bob.meeting.stats);
    });

    it('leave on the meeting object', () => Promise.all([
      testUtils.delayedPromise(bob.meeting.leave()),
      testUtils.waitForEvents([
        {scope: alice.meeting.members, event: 'members:update', user: alice},
        {scope: bob.webex.meetings, event: 'meeting:removed', user: bob},
        {scope: alice.webex.meetings, event: 'meeting:removed', user: alice}
      ])
    ])
      .then((response) => {
        assert.equal(bob.meeting, null);
        assert.equal(alice.meeting, null);

        console.log('RESPONSE ALL ', response);
      })
      .then(() => testUtils.waitForCallEnded(bob, alice.emailAddress))
      .then(() => testUtils.waitForCallEnded(alice, bob.emailAddress))
      .then(() => {
        assert.equal(alice.webex.meetings.getMeetingByType('sipUri', bob.emailAddress), null);
        assert.equal(bob.webex.meetings.getMeetingByType('sipUri', alice.emailAddress), null);
      }));
  });
});
