using System.Net.Http.Json;
using Common.Security.Infrastructure;
using Modules.User.Application.Interfaces;
using Modules.User.Application.Interfaces.Services;
using Modules.User.Application.Models;
using Modules.User.Application.Models.User.Session;

namespace Modules.User.Application.Services;

public class LocationService(UserContext userContext, IHttpClientFactory httpClientFactory) : ILocationService
{
    private const string _externalServiceUrl = "http://ip-api.com/json/";
    private async Task<Location?> GetLocationByIp(string? ip, bool throwException = true,
        CancellationToken cancellationToken = default)
    {
        if (string.IsNullOrWhiteSpace(ip))
        {
            var userContextIp = userContext.GetClientIp();
            if (string.IsNullOrWhiteSpace(userContextIp))
            {
                if (throwException) throw new Exception("Ip is null");
                return null;
            }
            ip = userContextIp;
        }
        
        using var client = httpClientFactory.CreateClient();
        try
        {
            var url = string.Concat(_externalServiceUrl, ip);
            var response = await client.GetFromJsonAsync<IpApiResponse>(url, cancellationToken);
            return response is not { lat: not null } || !response.lon.HasValue
                ? null
                : new Location
                {
                    Latitude = response.lat.Value,
                    Longitude = response.lon.Value,
                    Country = response.country,
                    Region = response.regionName,
                };
        }
        catch (Exception ex)
        {
            if (throwException) throw new Exception("Error while retrieving location", ex);
        }
        return null;
    }
    
    
    public async ValueTask<Location> GetLocationAsync(string? ip, CancellationToken cancellationToken)
    {
        var location = await GetLocationByIp(ip, cancellationToken:cancellationToken);
        return location!;

    }

    public async ValueTask<Location?> TryGetLocationAsync(string? ip, CancellationToken cancellationToken)
    {
        var location = await GetLocationByIp(ip, throwException:false, cancellationToken:cancellationToken);
        return location;
    }
    
    internal 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; }
    }
}