using MediatR;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Options;
using Modules.User.Application.Models;
using Modules.User.Application.Queries;
using Modules.User.Application.Settings;
using MyCSharp.HttpUserAgentParser.Providers;
using System.Net.Http;
using System.Net.Http.Json;
using System.Security.Claims;
using System.Text;

namespace Modules.User.Application;

public class UserContext
{
    private readonly IMediator _mediator;
    //private readonly IHttpContextAccessor _httpContextAccessor;
    private readonly JwtSettings _jwtSettings;
    private Models.User? _user;
    private string? _userAgent;
    private string? _refreshToken;
    private string? _clientIp;
    private readonly IHttpClientFactory _httpClientFactory;
    private readonly IHttpUserAgentParserProvider _userAgentParser;

    //public UserContext(IOptionsMonitor<JwtSettings> jwtSettingsOptions, IMediator mediator, IHttpContextAccessor httpContextAccessor, IConfiguration configuration)
    //public UserContext(IOptionsMonitor<JwtSettings> jwtSettingsOptions, IMediator mediator, IHttpContextAccessor httpContextAccessor, IHttpClientFactory httpClientFactory)
    public UserContext(IOptionsMonitor<JwtSettings> jwtSettingsOptions, IMediator mediator, IHttpClientFactory httpClientFactory,
        IHttpUserAgentParserProvider userAgentParser)
    {
        _mediator = mediator;
        //_httpContextAccessor = httpContextAccessor;
        _userAgentParser = userAgentParser;
        _jwtSettings = jwtSettingsOptions.CurrentValue;
        _httpClientFactory = httpClientFactory;
    }

    public JwtSettings GetJwtSettings() => _jwtSettings;

    internal void SetUserInfo(HttpContext httpContext, CancellationToken cancellationToken = default)
    {
        if (_user == null || !_user.IsAuthenticated)
        {
            if (Guid.TryParse(httpContext.User.FindFirstValue(Constants.AccountIdKey), out Guid accountId))
                _user = new Models.User
                {
                    AccountId = accountId,
                    SessionId = Guid.TryParse(httpContext.User.FindFirstValue(Constants.SessionIdKey), out Guid sessionId) ? sessionId : null,
                };
            else _user = new Models.User();
            //await RetrieveUserInfo(_user);
            if (!_user.IsAuthenticated) { }
        }
    }

    public async Task<Models.User> GetUserInfo(CancellationToken cancellationToken = default)
    {
        if (_user == null)
        {
            return _user = new Models.User();
        }

        if (!_user.IsAuthenticated)
        {
            if (_user.AccountId.HasValue)
                _user = await _mediator.Send(new GetUserQuery
                {
                    AccountId = _user.AccountId.Value,
                    SessionId = _user.SessionId,
                }, cancellationToken);
            else _user = new Models.User();
            if (!_user.IsAuthenticated) { }
        }

        return _user;
    }

    //private async Task RetrieveUserInfo(Models.User user)
    //{
    //    var ip = GetClientIp();
    //    var ipAddressWithoutPort = ip?.Split(':')[0];
    //    var location = await GetLocation(ipAddressWithoutPort);
    //    var clientInfo = new ClientInfo
    //    {
    //        Ip = !string.IsNullOrWhiteSpace(ipAddressWithoutPort) ? ipAddressWithoutPort : null,
    //        UserAgent = _httpContextAccessor.HttpContext?.Request.Headers.UserAgent,
    //        Location = location,
    //    };
    //    user.ClientInfo = clientInfo;
    //}

    internal void SetUserAgent(HttpContext httpContext) =>
        _userAgent = httpContext.Request.Headers.UserAgent;

    //internal string? GetUserAgent() => _httpContextAccessor.HttpContext?.Request.Headers.UserAgent;
    internal string? GetUserAgent()
    {
        if (string.IsNullOrWhiteSpace(_userAgent))
        {
            return null;
        }

        var parsedUseragent = _userAgentParser.Parse(_userAgent.ToString());
        var sb = new StringBuilder();
        if (!string.IsNullOrWhiteSpace(parsedUseragent.MobileDeviceType))
        {
            sb.Append(string.Concat("Mobile device: ", parsedUseragent.MobileDeviceType, ", "));
        }
        sb.Append(string.Concat(parsedUseragent.Name, " v", parsedUseragent.Version));
        if (!string.IsNullOrWhiteSpace(parsedUseragent.Platform?.Name))
        {
            sb.Append(string.Concat(", ", parsedUseragent.Platform.Value.Name));
        }

        return sb.ToString();
    }

    internal void SetRefreshToken(HttpContext httpContext)
    {
        if (httpContext.Request.Cookies.TryGetValue(TokenGenerator.RefreshTokenName, out string? refreshToken))
        {
            _refreshToken = refreshToken;
        }
        else
        {
            _refreshToken = null;
        }
    }

    //internal string? GetRefreshToken(string refreshTokenName) => _httpContextAccessor.HttpContext?.Request
    internal string? GetRefreshToken() => _refreshToken;

    internal void SetClientIp(HttpContext httpContext)
    {
        var ip = httpContext.GetServerVariable("HTTP_X_FORWARDED_FOR");
        if (string.IsNullOrWhiteSpace(ip)) ip = httpContext.Request.Headers["CF-CONNECTING-IP"];
        if (string.IsNullOrWhiteSpace(ip)) ip = httpContext.Connection.RemoteIpAddress?.ToString();
        _clientIp = ip?.Trim();
    }
    
    private string? GetClientIp() => _clientIp;

    //вынести в сервис
    internal async Task<Location?> GetLocation(string? ip)
    {
        if (string.IsNullOrWhiteSpace(ip))
        {
            ip = GetClientIp();
            if (string.IsNullOrWhiteSpace(ip))
            {
                return null;
            }
        }
        
        //var client = new HttpClient() { BaseAddress = new Uri("http://ip-api.com") };
        var client = _httpClientFactory.CreateClient();
        try
        {
            //var response = await client.GetFromJsonAsync<IpApiResponse>($"/json/{ip}");
            var response = await client.GetFromJsonAsync<IpApiResponse>($"http://ip-api.com/json/{ip}");
            return response == null || (!response.lat.HasValue || !response.lon.HasValue) ? null : new Location
            {
                //Latitude = response.lat.Value,
                //Longutude = response.lon.Value,
                Country = response.country,
                Region = response.regionName,
            };
        }
        catch { return null; }
    }

    public sealed class IpApiResponse
    {
        public string? status { get; set; }
        public string? continent { get; set; }
        public string? country { get; set; }
        public string? regionName { get; set; }
        public string? city { get; set; }
        public string? district { get; set; }
        public string? zip { get; set; }
        public double? lat { get; set; }
        public double? lon { get; set; }
        public string? isp { get; set; }
        public string? query { get; set; }
    }
}