using System.Net.WebSockets;
using System.Text;
using System.Text.Json;
using HaWeb;
using HaWeb.FileHelpers;
using HaWeb.Models;
using HaWeb.XMLParser;
using Microsoft.AspNetCore.Connections;
using Microsoft.FeatureManagement;

public class WebSocketMiddleware : IMiddleware {
    internal enum ValidationState {
        False,
        Parsing,
        True
    }

    internal class FileState {
        public ValidationState ValidationState { get; private set; }

        public FileState(ValidationState state) {
            this.ValidationState = state;
        }

        public FileState(XMLParsingState? state) {
            if (state == null) ValidationState = ValidationState.Parsing;
            else if (state.ValidState == true) ValidationState = ValidationState.True;
            else ValidationState = ValidationState.False;
        }
    }

    private readonly IFeatureManager _featureManager;
    private readonly IXMLInteractionService _xmlService;
    private readonly IXMLFileProvider _xmlProvider;

    private List<WebSocket>? _openSockets;

    public WebSocketMiddleware(IXMLFileProvider xmlprovider, IXMLInteractionService xmlservice, IFeatureManager featuremanager){
        this._xmlProvider = xmlprovider;
        this._xmlService = xmlservice;
        this._featureManager = featuremanager;
        if (_openSockets == null) _openSockets = new List<WebSocket>();
        _Subscribe();
    }

    public async Task InvokeAsync(HttpContext context, RequestDelegate requestDelegate) {
        if (!context.WebSockets.IsWebSocketRequest || context.Request.Path != "/WS") {
            // this case works perfectly fine for regular REST, middleware gets called.
            await requestDelegate.Invoke(context);
            return;
        }
        
        if (await _featureManager.IsEnabledAsync(Features.Notifications)) {
            using (WebSocket webSocket = await context.WebSockets.AcceptWebSocketAsync()) {
                await HandleConnection(context, webSocket);
            }
        }
    }

    private void _Subscribe() {
        _xmlProvider.FileChange += _HandleFileChange;
        _xmlProvider.NewState += _HandleNewState;
        _xmlProvider.NewData += _HandleNewData;
        _xmlProvider.ConfigReload += _HandleConfigReload;
        _xmlService.SyntaxCheck += _HandleSyntaxCheck;
    }

    private async Task HandleConnection(HttpContext context, WebSocket webSocket) {
        var buffer = new byte[1024 * 4];
        _openSockets!.Add(webSocket);
        WebSocketReceiveResult result = await webSocket.ReceiveAsync(new ArraySegment<byte>(buffer), CancellationToken.None);
        while (!result.CloseStatus.HasValue) {
            var msg = Encoding.UTF8.GetString(buffer,0,result.Count);
            try {
                if (msg.ToLower() == "hello") {
                    var state = _xmlProvider.GetGitState();
                    await webSocket.SendAsync(_SerializeToBytes(state), WebSocketMessageType.Text, true, CancellationToken.None);
                    await webSocket.SendAsync(_SerializeToBytes(new FileState(_xmlService.GetState())), WebSocketMessageType.Text, true, CancellationToken.None);
                } else if (msg.ToLower() == "ping" ) {
                    await webSocket.SendAsync(_SerializeToBytes(new { Ping = true}), WebSocketMessageType.Text, true, CancellationToken.None);
                }
                result = await webSocket.ReceiveAsync(new ArraySegment<byte>(buffer), CancellationToken.None);
            } catch (WebSocketException ex) {
                _openSockets!.Remove(webSocket);
            }
        }
        try {
            await webSocket.CloseAsync(result.CloseStatus.Value, result.CloseStatusDescription, CancellationToken.None);
        } catch (WebSocketException ex) {
            _openSockets.Remove(webSocket);
        }
        _openSockets!.Remove(webSocket);
    }

    private async void _HandleFileChange(object? sender, GitState? state) {
        await _SendToAll(state);
        await _SendToAll(new FileState(ValidationState.Parsing));
    }

    private async void _HandleNewState(object? sender, XMLParsingState? state) {
        if (state == null || !state.ValidState)
            await _SendToAll(new FileState(state));
    }

    private async void  _HandleNewData(object? sender, EventArgs _) {
        await _SendToAll(new { reload = true });
    }

    private async void _HandleConfigReload(object? sender, EventArgs _) {
        await _SendToAll(new { configreload = true });
    }

    private async void _HandleSyntaxCheck(object? sender, Dictionary<string, SyntaxCheckModel>? state) {
        if (state != null && state.Any()) {
            foreach (var c in state) 
                if (c.Value.Errors != null) {
                    await _SendToAll(new { SC = false });
                    return;
                }
            await _SendToAll(new { SC = true });
        }
        await _SendToAll(new { SC = (String?)null });
    }

    private async Task _SendToAll<T>(T msg) {
        if (_openSockets == null) return;
        foreach (var socket in _openSockets) {
            try {
                await socket.SendAsync(_SerializeToBytes(msg), WebSocketMessageType.Text, true, CancellationToken.None);
            } catch (WebSocketException ex) {
                _openSockets.Remove(socket);
            }
        }
    }

    private ArraySegment<byte> _SerializeToBytes<T>(T o) {
        var json = JsonSerializer.Serialize<T>(o);
        if (String.IsNullOrWhiteSpace(json)) {
            return new ArraySegment<byte>(new byte[] { }, 0, 0);
        }
        var bytes = Encoding.UTF8.GetBytes(json);
        return new ArraySegment<byte>(bytes, 0, bytes.Length);
    }
}