using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using PhilExampleCrawler.Common.Models;
using PhilExampleCrawler.Common.TCP.Enums;
using PhilExampleCrawler.Common.TCP.Packets;

namespace PhilExampleCrawler.TCPAPI.Services
{
    internal class UserSessionService
    {
        internal IReadOnlyDictionary<int, UserSession> RunningSessions => new Dictionary<int, UserSession>(_openSessions);

        /// <summary> represents connectionID, UserSession </summary>
        private readonly Dictionary<int, UserSession> _openSessions = new();
        private readonly List<UserSession> _suspendedSessions = new();
        private static int _lastSessionID = 0;
        private static int NextSessionID() => _lastSessionID++;
        private static DateTime NextValidUntil() => DateTime.Now.AddMinutes(60);


        internal UserSessionService()
        {
            if (Program.TCPServer == null)
                throw new InvalidOperationException("Program.TCPServer must not be null when using UserService.");

            Program.TCPServer.OnDataReceived += TCPServer_OnDataReceived;
            Program.TCPServer.OnUserDisconnected += TCPServer_OnUserDisconnected;
        }

        private void TCPServer_OnUserDisconnected(object? sender, int connectionID)
        {
            if(_openSessions.TryGetValue(connectionID, out UserSession? sess))
            {
                if (sess == null)
                    _openSessions.Remove(connectionID);
                else
                {
                    _openSessions.Remove(connectionID);
                    _suspendedSessions.Add(sess);
                }
            }
        }

        private async void TCPServer_OnDataReceived(object? sender, (int connectionID, BasePacket bp) e)
        {
            if(e.bp.LoadData.Type == LoadType.RegisterUserSessionLoad && e.bp.LoadData is RegisterUserSessionLoad regLoad)
            {
                var newSess = await RegisterUserSession(e.connectionID, regLoad);

                if (newSess != null) {
                    if (Program.TCPServer.Send(e.connectionID,
                                               new BasePacket(
                                                    new RegisterUserSessionOKLoad()
                                                    {
                                                        ForAuthCode = regLoad.AuthCode,
                                                        ValidUntil = newSess.ValidUntil
                                                    })))
                        Console.WriteLine("sent back RegisterUserSessionOKLoad to connectionID:" + e.connectionID);
                    else
                        Console.WriteLine("[Error2] TODO: could not send back RegisterUserSessionOKLoad to connectionID:" + e.connectionID);
                }
                else
                    Console.WriteLine("[Error2] TODO: could not send back RegisterUserSessionOKLoad to connectionID:" + e.connectionID);
            }
        }

        private async Task<UserSession?> RegisterUserSession(int connectionID, RegisterUserSessionLoad regLoad)
        {
            if(TryUnsuspendSession(regLoad.AuthCode, out UserSession? unsuspendedSess) && unsuspendedSess != null)
            {
                _openSessions.Add(connectionID, unsuspendedSess);
                return unsuspendedSess;
            }

            var sessID = NextSessionID();
            var validUntil = NextValidUntil();

            var user = await Program.DB_UserAcces.GetUserAsync(regLoad.AuthCode);
            var crawlSessions = await Program.DB_CrawlSessionAccess.GetCrawlSessionsAsync(user.ID);
            var newSess = new UserSession(sessID, user, crawlSessions, validUntil);
            if (_openSessions.TryGetValue(connectionID, out UserSession? _))
                _openSessions[connectionID] = newSess;
            else
                _openSessions.Add(connectionID, newSess);

            return newSess;
        }


        /// <summary> Removes a session with given authCode from _suspendedSessions and updates its ValidUntil </summary>
        private bool TryUnsuspendSession(string authCode, out UserSession? unsuspendedSess)
        {
            unsuspendedSess = _suspendedSessions.FirstOrDefault(x => x.User.AuthCode == authCode);
            if (unsuspendedSess != null)
            {
                unsuspendedSess.ValidUntil = NextValidUntil();
                _suspendedSessions.Remove(unsuspendedSess);
                return true;
            }
            else
                return false;
        }
    }
}