import 'package:flutter_test/flutter_test.dart';
import 'package:gmus/actions.dart';
import 'package:gmus/controller.dart';
import 'package:gmus/socket.dart';
import 'package:mockito/mockito.dart';
import 'package:web_socket_channel/io.dart';
import 'package:web_socket_channel/web_socket_channel.dart';

void main() {
  group('Controller', () {
    group('Receiving messages from pubsub', () {
      group(CLIENT_LIST_UPDATED, () {
        String message = '{"type":"CLIENT_LIST_UPDATED","payload":[{"name":"client-A","lastPing":123},{"name":"client-B","lastPing":456}]}';

        test('client list should be updated', () {
          Controller controller = Controller();

          onRemoteMessage(controller, message);

          expect(controller.clients.length, 2);

          expect(controller.clients[0].name, 'client-A');
          expect(controller.clients[0].lastPing, 123);

          expect(controller.clients[1].name, 'client-B');
          expect(controller.clients[1].lastPing, 456);
        });
      });

      group(STATE_SET, () {
        String message = '{"type":"STATE_SET","payload":{"master":"new-master-client","activeClients":["some-client"],"songId":7123,"currentTime":10.843,"seekTime":23.001,"playing":false,"queue":[9750],"shuffleMode":true}}';

        test('player state should be updated', () {
          Controller controller = Controller();
          onRemoteMessage(controller, message);

          expect(controller.player.value.master, 'new-master-client');
          expect(controller.player.value.songId, 7123);
          expect(controller.player.value.currentTime, 10.843);
          expect(controller.player.value.seekTime, 23.001);
          expect(controller.player.value.playing, false);
          expect(controller.player.value.shuffleMode, true);
        });

        test('queue should be updated', () {
          Controller controller = Controller();
          onRemoteMessage(controller, message);

          expect(controller.player.value.queue.length, 1);
          expect(controller.player.value.queue[0], 9750);
        });

        test('active clients should be updated', () {
          Controller controller = Controller();
          onRemoteMessage(controller, message);

          expect(controller.player.value.activeClients.length, 1);
          expect(controller.player.value.activeClients[0], 'some-client');
        });
      });
    });

    group('Play / pause', () {
      group('When slave', () {
        group('when paused', () {
          test('should send a "play" action to pubsub', () {
            var controller = mockControllerAsSlave();
            controller.player.value.playing = false;

            controller.playPause();

            verify(controller.socket.channel.sink.add('{"type":"STATE_SET","payload":{"songId":null,"playing":true,"currentTime":0.0,"seekTime":-1.0,"master":"other-client-name-master","activeClients":[],"queue":[],"shuffleMode":false}}')).called(1);
          });
        });

        group('when playing', () {
          test('should send a "pause" action to pubsub', () {
            var controller = mockControllerAsSlave();
            controller.player.value.playing = true;
            controller.player.value.songId = 182;

            controller.playPause();

            verify(controller.socket.channel.sink.add('{"type":"STATE_SET","payload":{"songId":182,"playing":false,"currentTime":0.0,"seekTime":-1.0,"master":"other-client-name-master","activeClients":[],"queue":[],"shuffleMode":false}}')).called(1);
          });
        });
      });

      group('When master', () {
        test('should not send any actions to pubsub', () {
          var controller = mockControllerAsMaster();
          controller.player.value.playing = false;
          controller.playPause();

          verifyNever(controller.socket.channel.sink.add('{"type":"STATE_SET","payload":{"songId":null,"playing":true,"currentTime":0.0,"seekTime":-1.0,"master":"other-client-name-master","activeClients":[],"queue":[],"shuffleMode":false}}'));
        });
      });
    });

    group('Playing a song', () {
      group('When slave', () {
        test('should send an action to pubsub to play the song', () {
          var controller = mockControllerAsSlave();

          controller.playSong(871);

          verify(controller.socket.channel.sink.add('{"type":"STATE_SET","payload":{"songId":871,"playing":true,"currentTime":0.0,"seekTime":-1.0,"master":"other-client-name-master","activeClients":[],"queue":[],"shuffleMode":false}}')).called(1);
        });
      });

      group('When master', () {
        test('should throw an error', () {
          var controller = mockControllerAsMaster();

          expect(() => controller.playSong(871), throwsException);
        });
      });
    });
  });
}

class MockWebSocketSink extends Mock implements WebSocketSink {}

class MockChannel extends Mock implements IOWebSocketChannel {
  final WebSocketSink sink;
  MockChannel({
    this.sink,
  });
}

Controller mockControllerAsMaster() {
  Controller controllerAsMaster = Controller();
  controllerAsMaster.uniqueName.value = 'my-client-name-master';
  controllerAsMaster.player.value.master = 'my-client-name-master';

  controllerAsMaster.socket.channel = MockChannel(sink: MockWebSocketSink());

  return controllerAsMaster;
}

Controller mockControllerAsSlave() {
  Controller controllerAsSlave = Controller();
  controllerAsSlave.uniqueName.value = 'my-client-name-slave';
  controllerAsSlave.player.value.master = 'other-client-name-master';

  controllerAsSlave.socket.channel = MockChannel(sink: MockWebSocketSink());

  return controllerAsSlave;
}
