using Insight.Infrastructure;
using Insight.Web.Models;
using Microsoft.AspNetCore.Components.Authorization;
using Microsoft.AspNetCore.Components.Server.Circuits;
using MongoDB.Driver;

namespace Insight.Web.Services;

public class SessionHandler : CircuitHandler
{
    public SessionState State { get; } = new();
    
    private readonly ChatService _chatService;
    private readonly SessionPool _sessionPool;
    private readonly AuthenticationStateProvider _authenticationState;
    private readonly IMongoDatabase _database;
    private readonly IHttpContextAccessor _httpContextAccessor;
    private readonly ILogger<SessionHandler> _logger;

    public SessionHandler(
        ChatService chatService,
        SessionPool sessionCache,
        AuthenticationStateProvider authenticationStateProvider,
        IMongoDatabase database,
        IHttpContextAccessor httpContextAccessor,
        ILogger<SessionHandler> logger)
    {
        _chatService = chatService;
        _authenticationState = authenticationStateProvider;
        _sessionPool = sessionCache;
        _database = database;
        _httpContextAccessor = httpContextAccessor;
        _logger = logger;
    }

    public override async Task OnCircuitOpenedAsync(Circuit circuit, CancellationToken cancellationToken)
    {
        // capture current state
        var authState = await _authenticationState.GetAuthenticationStateAsync();

        State.Id = circuit.Id;
        State.Connected = true;
        State.Username = authState?.User?.Identity?.Name;
        State.Authenticated = authState?.User?.Identity?.IsAuthenticated ?? false;

        if (State.Username is not null)
        {
            var user = await _database.User().Find(p => p.UserName == State.Username).FirstOrDefaultAsync(cancellationToken);
            State.Uid = user.Id;
        }

        await base.OnCircuitOpenedAsync(circuit, cancellationToken);

        await _sessionPool.AddAsync(State, cancellationToken);
        await _chatService.AddAsync(State, cancellationToken);
    }

    public override async Task OnCircuitClosedAsync(Circuit circuit, CancellationToken cancellationToken)
    {
        await _sessionPool.RemoveAsync(State, cancellationToken);

        await base.OnCircuitClosedAsync(circuit, cancellationToken);
    }

    public override async Task OnConnectionUpAsync(Circuit circuit, CancellationToken cancellationToken)
    {
        _logger.LogInformation($"Session up: {circuit.Id}");

        State.Connected = true;

        await UpdateStateAsync(cancellationToken);
        await base.OnConnectionUpAsync(circuit, cancellationToken);

        await _sessionPool.UpdateAsync(State, cancellationToken);
        await _chatService.AddAsync(State, cancellationToken);
    }

    public override async Task OnConnectionDownAsync(Circuit circuit, CancellationToken cancellationToken)
    {
        _logger.LogInformation($"Session down: {circuit.Id}");

        State.Connected = false;

        await base.OnConnectionDownAsync(circuit, cancellationToken);

        _httpContextAccessor?.HttpContext?.Abort();

        await _sessionPool.UpdateAsync(State, cancellationToken);
        await _chatService.RemoveAsync(State, cancellationToken);
    }

    public async Task UpdateStateAsync(Circuit circuit, CancellationToken cancellationToken)
    {
        if (circuit.Id is null) return;

        // get current authentication state
        var authState = await _authenticationState.GetAuthenticationStateAsync();

        // set auth state
        if (State.Authenticated != authState?.User?.Identity?.IsAuthenticated)
        {
            State.Authenticated = authState?.User?.Identity?.IsAuthenticated ?? false;
            await _sessionPool.UpdateAsync(State, cancellationToken);
        }

        // set user
        if (State.Username != authState?.User?.Identity?.Name)
        {
            State.Username = authState?.User?.Identity?.Name;
            await _sessionPool.UpdateAsync(State, cancellationToken);
        }
    }

    public async Task UpdateStateAsync(CancellationToken cancellationToken)
    {
        if (State.Id is null) return;

        // get current authentication state
        var authState = await _authenticationState.GetAuthenticationStateAsync();

        // set auth state
        if (State.Authenticated != authState?.User?.Identity?.IsAuthenticated)
        {
            State.Authenticated = authState?.User?.Identity?.IsAuthenticated ?? false;
            await _sessionPool.UpdateAsync(State, cancellationToken);
        }

        // set user
        if (State.Username != authState?.User?.Identity?.Name)
        {
            State.Username = authState?.User?.Identity?.Name;
            await _sessionPool.UpdateAsync(State, cancellationToken);
        }
    }
}