using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging;
using System.Net;
using System.Net.WebSockets;
using System.Text;

namespace EVCB_OCPP.WSServer.Service.WsService;

public class WsSession
{
    public WsSession(ILogger<WsSession> logger)
    {
        this.logger = logger;
    }

    public PathString? Path { get; set; }
    public string UriScheme { get; set; }
    public string SessionID { get; set; }
    public IPEndPoint Endpoint { get; internal set; }
    public DateTime LastActiveTime { get; set; }


    private WebSocket _WebSocket;
    public WebSocket ClientWebSocket
    {
        get => _WebSocket;
        set
        {
            Init(value);
        }
    }

    public WebSocketState State => ClientWebSocket.State;
    public string SecWebSocketProtocol => ClientWebSocket.SubProtocol;

    public SemaphoreSlim EndConnSemaphore { get; } = new SemaphoreSlim(0);

    //public event OCPPClientDataEventHandler<WsSession, String> m_ReceiveData;

    public event EventHandler<string> SessionClosed;

    private CancellationTokenSource disconnectCancellationTokenSource = new CancellationTokenSource();
    private Task ReceiveLoopTask;
    private readonly ILogger<WsSession> logger;

    private void Init(WebSocket webSocket)
    {
        _WebSocket = webSocket;
        LastActiveTime = DateTime.UtcNow;
        ReceiveLoopTask = StartReceivd(webSocket, disconnectCancellationTokenSource.Token);
    }

    private async Task StartReceivd(WebSocket webSocket, CancellationToken token)
    {
        logger.LogInformation("{id} {func} {Path} Start", SessionID, nameof(StartReceivd), Path);

        byte[] receivdBuffer = new byte[0];
        int bufferExpand = 1;
        int receivedBytes = 0;
        while (!token.IsCancellationRequested)
        {
            var tempReceiveBuffer = new byte[1024 * 4];
            WebSocketReceiveResult result = null;

            try
            {
                result = await webSocket.ReceiveAsync(new ArraySegment<byte>(tempReceiveBuffer), token);
            }
            catch (Exception e)
            {
                _ = BruteClose(e.Message);
                break;
            }
            LastActiveTime = DateTime.UtcNow;

            if (result == null || result.CloseStatus.HasValue)
            {
                //closed gracefully
                await GracefulClose(result.CloseStatus.Value);
                break;
            }

            receivdBuffer = new byte[1024 * 4 * bufferExpand];
            Array.Copy(tempReceiveBuffer, 0, receivdBuffer, receivedBytes, result.Count);
            receivedBytes += result.Count;

            if (!result.EndOfMessage)
            {
                bufferExpand++;
                continue;
            }

            var received = Encoding.UTF8.GetString(receivdBuffer, 0, receivedBytes);
            //logger.LogInformation("{func}:{Path} {value}", nameof(StartReceivd), Path, received);

            HandleReceivedData(received);

            bufferExpand = 1;
            receivedBytes = 0;
        }
    }

    internal virtual void HandleReceivedData(string data)
    {

    }

    internal Task Send(string dataString)
    {
        //logger.LogInformation("{func}:{Path} {value}", nameof(Send), Path, dataString);

        var data = Encoding.UTF8.GetBytes(dataString);
        return Send(data);
    }

    internal Task Close()
    {
        return ServerClose();
    }

    private async Task Send(byte[] data)
    {
        try
        {
            await ClientWebSocket.SendAsync(data, WebSocketMessageType.Text, endOfMessage: true, cancellationToken: disconnectCancellationTokenSource.Token);
        }
        catch (Exception e)
        {
            logger.LogInformation("{func} {Path} exception:{msg}", nameof(Send), Path, e.Message);
        }
    }

    private Task ServerClose()
    {
        logger.LogInformation("{func}:{Path}", nameof(ServerClose), Path);

        SessionClosed?.Invoke(this, "ServerShutdown");
        return InternalClose(WebSocketCloseStatus.NormalClosure, "ServerShutdown");
    }

    private  Task GracefulClose(WebSocketCloseStatus closeStatus)
    {
        logger.LogInformation("{func}:{Path} {value}", nameof(GracefulClose), Path, closeStatus);

        SessionClosed?.Invoke(this, closeStatus.ToString());
        return InternalClose(closeStatus , null);
    }

    private Task BruteClose(string description)
    {
        logger.LogInformation("{func}:{Path} {value}", nameof(ServerClose), Path, description);

        SessionClosed?.Invoke(this, description);
        return InternalClose(WebSocketCloseStatus.EndpointUnavailable, description);
    }

    private async Task InternalClose(WebSocketCloseStatus closeStatus, string description)
    {
        try
        {
            await _WebSocket.CloseAsync(closeStatus, description, default);
        }
        catch
        { 
        }
        finally
        {
            _WebSocket.Dispose();
        }

        disconnectCancellationTokenSource.Cancel();
        EndConnSemaphore.Release();
    }
}
