
import 'dart:convert';

import 'package:bus_infotainment/auth/api_constants.dart';
import 'package:bus_infotainment/backend/live_information.dart';
import 'package:bus_infotainment/tfl_datasets.dart';
import 'package:bus_infotainment/utils/audio%20wrapper.dart';
import 'package:bus_infotainment/utils/delegates.dart';
import 'package:appwrite/appwrite.dart' as appwrite;
import 'package:appwrite/models.dart' as models;
import 'package:uuid/uuid.dart';

import '../../auth/auth_api.dart';
import 'info_module.dart';

class CommandModule extends InfoModule {

  final String sessionID;
  late final String clientID;

  List<CommandInfo> _commandHistory = [];
  get commandHistory => _commandHistory;
  EventDelegate<CommandInfo> onCommandReceived = EventDelegate();

  CommandModule(this.sessionID){
    // generate a random client ID
    var uuid = Uuid();

    clientID = uuid.v4();

    if (liveInformation.auth.isAuthenticated()){
      print("Auth is authenticated");
      _setupListener();
    } else {
      print("Auth is not authenticated");
      liveInformation.auth.onLogin.addListener((value) {
        _setupListener();
      });
    }
  }

  // Will execute the command an event which is triggered when a response is received
  Future<EventDelegate> executeCommand(String command) async {
    EventDelegate<String> delegate = EventDelegate();

    final client = liveInformation.auth.client;

    final databases = appwrite.Databases(client);

    if (true) {
      try {
        final response = await databases.listDocuments(
            databaseId: "6633e85400036415ab0f",
            collectionId: "6633e85d0020f52f3771",
            queries: [
              appwrite.Query.search("SessionID", liveInformation.roomCode!)
            ]
        );

        List<String> pastCommands = [];

        response.documents.first.data["Commands"].forEach((element) {
          pastCommands.add(element);
        });

        pastCommands.add(command);

        final document = await databases.updateDocument(
          databaseId: "6633e85400036415ab0f",
          collectionId: "6633e85d0020f52f3771",
          documentId: liveInformation.roomDocumentID!,
          data: {
            "Commands": pastCommands,
            "LastUpdater": clientID,
          }
        );
      } catch (e) {
        print("Failed to send command");

      }
    }

    _onCommandReceived(CommandInfo(command, clientID));

    return delegate;
  }

  Future<void> _onCommandReceived(CommandInfo commandInfo) async {

    commandHistory.add(commandInfo);
    onCommandReceived.trigger(commandInfo);

    print("Received command: ${commandInfo.command}");

    List<String> commandParts = splitCommand(commandInfo.command);
    String command = commandParts[0];
    List<String> args = commandParts.sublist(1);

    if (command == "Response:") {

    }
    else if (command == "initroom") {
      // initroom <roomCode>

    }
    else if (command == "announce") {

      final displayText = args[1];

      if (args[0] == "manual") {
        // announce manual <DisplayText> <AudioFileName>... <ScheduledTime>


        List<String> audioFileNames = args.sublist(2);
        try {
          if (int.parse(audioFileNames.last) != null) {
            audioFileNames.removeLast();
          }
        } catch (e) {}

        DateTime scheduledTime = LiveInformation().syncedTimeModule.Now().add(Duration(seconds: 1));
        try {
          if (int.parse(args.last) != null) {
            scheduledTime = DateTime.fromMillisecondsSinceEpoch(int.parse(args.last));
          }
        } catch (e) {}

        liveInformation.announcementModule.queueAnnounceByAudioName(
          displayText: displayText,
          audioNames: audioFileNames,
          scheduledTime: scheduledTime,
          sendToServer: false
        );

      }
      else if (args[0] == "info") {

        int InfoIndex = int.parse(args[1]);

        DateTime scheduledTime = LiveInformation().syncedTimeModule.Now();
        try {
          if (int.parse(args.last) != null) {
            scheduledTime = DateTime.fromMillisecondsSinceEpoch(int.parse(args.last));
          }
        } catch (e) {}

        liveInformation.announcementModule.queueAnnouncementByInfoIndex(
          infoIndex: InfoIndex,
          scheduledTime: scheduledTime,
          sendToServer: false
        );
      }
      else if (args[0].startsWith("dest")) {
        // announce destination <RouteNumber> <RouteVariantIndex> <ScheduledTime>

        print("Checkpoint 1");

        String routeNumber = args[1];
        int routeVariantIndex = int.parse(args[2]);

        DateTime scheduledTime = LiveInformation().syncedTimeModule.Now();
        try {
          if (int.parse(args.last) != null) {
            scheduledTime = DateTime.fromMillisecondsSinceEpoch(int.parse(args.last));
          }
        } catch (e) {}

        print("Checkpoint 2");
        BusRoute route = LiveInformation().busSequences.routes[routeNumber]!;
        BusRouteVariant routeVariant = route.routeVariants.values.toList()[routeVariantIndex];

        print("Checkpoint 3");
        liveInformation.announcementModule.queueAnnouncementByRouteVariant(
          routeVariant: routeVariant,
          scheduledTime: scheduledTime,
          sendToServer: false
        );

      }

    }
    else if (command == "setroute") {
      // setroute <RouteNumber> <RouteVariantIndex>

      LiveInformation liveInformation = LiveInformation();

      String routeNumber = args[0];
      int routeVariantIndex = int.parse(args[1]);

      BusRoute route = liveInformation.busSequences.routes[routeNumber]!;
      BusRouteVariant routeVariant = route.routeVariants.values.toList()[routeVariantIndex];

      liveInformation.setRouteVariant(
        routeVariant
      );


      // Update the server
      if (liveInformation.isHost) {
        print("Updating server");
        final client = liveInformation.auth.client;
        final databases = appwrite.Databases(client);

        final response = await databases.listDocuments(
            databaseId: "6633e85400036415ab0f",
            collectionId: "6633e85d0020f52f3771",
            queries: [
              appwrite.Query.search("SessionID", liveInformation.roomCode!)
            ]
        );

        final document = await databases.updateDocument(
            databaseId: "6633e85400036415ab0f",
            collectionId: "6633e85d0020f52f3771",
            documentId: response.documents.first.$id,
            data: {
              "CurrentRoute": routeNumber,
              "CurrentRouteVariant": routeVariantIndex,
            }
        );
        try {

          print("Updated server");
        } catch (e) {
          print("Failed to update server");
        }
      }

    }

  }

  appwrite.RealtimeSubscription? _subscription;
  Future<void> _setupListener() async {
    if (_subscription != null) {
      return;
    }

    // final realtime = appwrite.Realtime(LiveInformation().auth.client);
    //
    // _subscription = realtime.subscribe(
    //   ['databases.${ApiConstants.INFO_Q_DATABASE_ID}.collections.${ApiConstants.COMMANDS_COLLECTION_ID}.documents']
    // );
    // _subscription!.stream.listen((event) {
    //   print(jsonEncode(event.payload));
    //
    //   // Only do something if the document was created or updated
    //   if (!(event.events.first.contains("create") || event.events.first.contains("update"))) {
    //     return;
    //   }
    //
    //   final commandInfo = CommandInfo(event.payload['command'], event.payload['client_id']);
    //
    //   if (commandInfo.clientID != clientID) {
    //     _onCommandReceived(commandInfo);
    //   }
    //
    // });

    print("Listening for commands");

    await Future.delayed(Duration(seconds: 90));

    await _subscription!.close();
    _subscription = null;
    _setupListener();

  }

}

class CommandInfo {
  final String command;
  final String clientID;

  CommandInfo(this.command, this.clientID);
}

List<String> splitCommand(String command) {
  var regex = RegExp(r'([^\s"]+)|"([^"]*)"');
  var matches = regex.allMatches(command);
  return matches.map((match) => match.group(0)!.replaceAll('"', '')).toList();
}