using System.Collections.Immutable;
using System.Reactive.Linq;

namespace InnovEnergy.Lib.Protocols.DBus.Daemon;

using NameOwnerChanged = ValueTuple<String, String, String>;
using ServiceDict = ImmutableDictionary<String, DBusService>;

public enum ServiceChange
{
    NoChange,
    IdAdded,
    IdRemoved,
    BusNameAdded,
    BusNameRemoved,
}

public sealed record ServiceChanged(ServiceChange Change,
                                           String BusNameOrId,
                                      DBusService Service,
         ImmutableDictionary<String, DBusService> AllServices);


public static class Resolver
{
    private static readonly ServiceChanged InitialState = NoChange(ImmutableDictionary<String, DBusService>.Empty);

    public static IObservable<ServiceChanged> ObserveServices(this DBusDaemonConnection connection)
    {
        var names = connection
                   .ListNames()
                   .Result;
        
        var newIds = names
                    .Where(DBusDaemonApi.IsBusId)
                    .Select(NewBusId);

        var newBusNames = names
                         .Where(DBusDaemonApi.IsBusName)
                         .Select(NewBusName)
                         .Where(t => t != null)
                         .Cast<NameOwnerChanged>();

        var ownerChanged = connection
                          .ObserveSignalMessages(DBusDaemonApi.ServiceName,
                                                 DBusDaemonApi.Interface,
                                                 DBusDaemonApi.ObjectPath,
                                                 "NameOwnerChanged")
                          .Select(m => m.Payload!)
                          .OfType<NameOwnerChanged>();

        var init = newIds.Concat(newBusNames).ToList();

        return ownerChanged
              .StartWith(init)
              .Scan(InitialState, OnNameOwnerChanged)
              .Where(c => c.Change != ServiceChange.NoChange);


        NameOwnerChanged? NewBusName(String busName)
        {
            var ownerId = connection
                         .GetNameOwner(busName)
                         .ContinueWith(t => t.IsCompletedSuccessfully ? t.Result : null)
                         .Result;

            if (ownerId is null)
                return null;

            return (busName, "", ownerId);
        }

    }

    private static ServiceChanged OnNameOwnerChanged(ServiceChanged serviceChange, NameOwnerChanged ownerChange)
    {
        var allServices = serviceChange.AllServices;

        if (ownerChange.IsInvalid())
            return NoChange(allServices);

        var (nameOrId, oldOwner, newOwner) = ownerChange;

        if (ownerChange.IdWasRemoved())
        {
            var id = nameOrId;

            allServices.TryGetValue(id, out var service);
            service ??= new DBusService(id);
            allServices = allServices.Remove(id);
            //Console.WriteLine("Removed " + id);
            return new ServiceChanged(ServiceChange.IdRemoved, id, service, allServices);
        }

        if (ownerChange.IdWasAdded())
        {
            var id = nameOrId;

            var service = new DBusService(id);
            allServices = allServices.SetItem(id, service);
            //Console.WriteLine("Added " + id);
            return new ServiceChanged(ServiceChange.IdAdded, id, service, allServices);
        }

        if (ownerChange.BusNameWasRemoved())
        {
            var busName = nameOrId;
            var id      = oldOwner;

            allServices.TryGetValue(busName, out var service);

            if (service == null)
            {
                Console.WriteLine("BusNameRemoved: should not happen");
                service = new DBusService(busName);
            }

            allServices = allServices.Remove(busName);

            if (allServices.TryGetValue(id, out var owner))
                owner.BusNames.Remove(busName);

            //Console.WriteLine("Removed " + busName);
            return new ServiceChanged(ServiceChange.BusNameRemoved, busName, service, allServices);
        }

        if (ownerChange.BusNameWasAdded())
        {
            var busName = nameOrId;
            var id      = newOwner;

            if (!allServices.TryGetValue(id, out var service))
            {
                Console.WriteLine("BusNameAdded: should not happen");
                service = new DBusService(id);
                allServices = allServices.Add(id,service);
            }

            service.BusNames.Add(busName);
            allServices = allServices.SetItem(busName, service);
            //Console.WriteLine("Added " + busName);
            return new ServiceChanged(ServiceChange.BusNameAdded, busName, service, allServices);
        }

        Console.WriteLine("NoChange: should not happen");

        return NoChange(allServices);
    }

    private static ServiceChanged NoChange(ServiceDict allServices)
    {
        return new ServiceChanged(ServiceChange.NoChange, null!, null!, allServices);
    }

    private static NameOwnerChanged NewBusId(String id) => (id, "", id);

    private static Boolean IsInvalid(this NameOwnerChanged change)
    {
        var (name, oldOwner, newOwner) = change;
        return name == "" || oldOwner == newOwner;
    }

    private static Boolean IdWasAdded(this NameOwnerChanged change)
    {
        var (id, oldOwner, newOwner) = change;
        return id.IsBusId() && oldOwner == "" && newOwner == id;
    }

    private static Boolean IdWasRemoved(this NameOwnerChanged change)
    {
        var (id, oldOwner, newOwner) = change;
        return id.IsBusId() && oldOwner == id && newOwner == "";
    }

    private static Boolean BusNameWasAdded(this NameOwnerChanged change)
    {
        var (name, _, newOwner) = change;
        return name.IsBusName() && newOwner.IsBusId();
    }

    private static Boolean BusNameWasRemoved(this NameOwnerChanged change)
    {
        var (name, oldOwner, _) = change;
        return name.IsBusName() && oldOwner.IsBusId();
    }

}