using e_suite.API.Common.repository;
using e_suite.Database.Core.Tables.Sentinel;
using eSuite.Core.Clock;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Configuration;

namespace e_suite.Service.Sentinel;

public class Sentinel : SentinelBase, ISentinel
{
    private const string AccessDenied = "Access denied";
    public Sentinel(IClock clock, IConfiguration configuration, ISentinelRepository sentinelRepository) : base(clock, configuration, sentinelRepository)
    {
    }

    public async Task LogBadRequest(ControllerBase controller, CancellationToken cancellationToken)
    {
        var failedLoginAttempt = new FailedAccessAttempt
        {
            IPAddress = GetIPAddress(controller),
            AttemptedTime = Clock.GetNow
        };

        await SentinelRepository.AddFailedAccessAttempt(failedLoginAttempt, cancellationToken);
    }

    private static string GetIPAddress(ControllerBase controller)
    {
        var ipAddress = controller.HttpContext.Request.Headers["X-Forwarded-For"].ToString();
        if (string.IsNullOrWhiteSpace(ipAddress))
            ipAddress = controller.HttpContext.Connection.RemoteIpAddress?.ToString();

        if (ipAddress == null)
            throw new NullReferenceException("Unable to find IP Address");

        ipAddress = RemovePort(ipAddress);
        return ipAddress;
    }

    private static string RemovePort(string ipAddress)
    {
        var portSeparatorIndex = ipAddress.IndexOf(":");
        if (portSeparatorIndex != -1)
        {
            return ipAddress.Substring(0, portSeparatorIndex);
        }

        return ipAddress;
    }

    public async Task<IActionResult?> CheckSecurity(ControllerBase controller, CancellationToken cancellationToken)
    {
        var (earliestAttemptTime, maxLoginAttempts) = GetLoginAttemptConstraints();

        var ipAddress = GetIPAddress(controller);

        var loginAttempts = await SentinelRepository.GetAccessAttemptsSince(ipAddress, earliestAttemptTime, cancellationToken);

        if (loginAttempts > maxLoginAttempts)
            return controller.Unauthorized(new ProblemDetails
                {
                    Title = AccessDenied,
                    Detail = "You are locked out. You can try again in 1hr or contact your admin."
            }
            );

        return null;
    }
}