using System;
using System.Collections.Generic;
using System.Collections.Concurrent;
using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using MaxLib.WebServer.WebSocket;

namespace Dacryptero.Web
{
    public class WebSocketConnection : EventConnection
    {
        private static List<WebSocketConnection> connections = new List<WebSocketConnection>();
        private static SemaphoreSlim connectionsLock = new SemaphoreSlim(1, 1);

        public static void Foreach(Action<WebSocketConnection> handler)
        {
            connectionsLock.Wait();
            try
            {
                foreach (var con in connections)
                    handler(con);
            }
            finally
            {
                connectionsLock.Release();
            }
        }

        public static async Task Foreach(Func<WebSocketConnection, Task> handler)
        {
            await connectionsLock.WaitAsync().ConfigureAwait(false);
            try
            {
                await Task.WhenAll(connections.Select(x => handler(x)))
                    .ConfigureAwait(false);
            }
            finally
            {
                connectionsLock.Release();
            }
        }

        public static async Task<WebSocketConnection?> Find(Func<WebSocketConnection, bool> filter)
        {
            await connectionsLock.WaitAsync().ConfigureAwait(false);
            try
            {
                foreach (var conn in connections)
                    if (filter(conn))
                        return conn;
                return null;
            }
            finally
            {
                connectionsLock.Release();
            }
        }

        public static async Task<WebSocketConnection?> FindFromSessionId(string id)
        {
            return await Find(x => x.Session.IdString == id);
        }

        public Session.ClientSession Session { get; } = new Session.ClientSession();

        public DB.DBMultiController DB { get; }

        /// <summary>
        /// The current unlocked databases. These information contains the decryption key. This is
        /// only needed for adding new user accounts to the databases. Remove the objects from this
        /// collection as fast as possible!
        /// </summary>
        public ConcurrentDictionary<string, DB.Creation.UnlockedDBInfo> UnlockedDBs { get; }
            = new ConcurrentDictionary<string, DB.Creation.UnlockedDBInfo>();

        /// <summary>
        /// The fido login information about the current login/registration process. There can
        /// only exists one at the same time for the same connection. Depending what is done (login
        /// or registration) this object can hold different data and do different things. The common
        /// thing is, that both process take use of the fido key and has special communication with
        /// it.
        /// </summary>
        public Session.Fido.UserBase? FidoLogin { get; set; }

        public ConcurrentDictionary<string, DB.Creation.NewDBInfo> NewDBInfo { get; }
            = new ConcurrentDictionary<string, DB.Creation.NewDBInfo>();

        public string Host { get; }

        public WebSocketConnection(Stream networkStream, EventFactory factory,
            DB.DBMultiController db, string host)
            : base(networkStream, factory)
        {
            DB = db;
            Host = host;
#if DEBUG
            var single = db.GetController("test");
            if (single is not null)
                Session.AddController(single);
            single = db.GetController("eth-test");
            if (single is not null)
                Session.AddController(single);
#endif
            Closed += (_, _) =>
            {
                connectionsLock.Wait();
                connections.Remove(this);
                connectionsLock.Release();
            };
            connectionsLock.Wait();
            connections.Add(this);
            connectionsLock.Release();
        }

        protected override Task ReceiveClose(CloseReason? reason, string? info)
        {
            return Task.CompletedTask;
        }

        protected override Task ReceivedFrame(EventBase @event)
        {
            _ = Task.Run(async () => 
            {
                switch (@event)
                {
                    case Events.ReceiveBase receive:
                        await receive.Execute(new Events.ExecuteArgs(
                            this
                        ));
                        break;
                }
            });
            return Task.CompletedTask;
        }

        public async Task Send<T>(T @event)
            where T : Events.SendBase
        {
            await SendFrame(@event).ConfigureAwait(false);
        }
    }
}