using System.Text;
using FitConnect.Encryption;
using FitConnect.Interfaces;
using FitConnect.Models;
using FitConnect.Services;
using FitConnect.Services.Interfaces;
using IdentityModel;
using Microsoft.Extensions.Logging;
using Microsoft.IdentityModel.JsonWebTokens;
using Microsoft.IdentityModel.Tokens;
using Newtonsoft.Json;
using Newtonsoft.Json.Serialization;
using Route = FitConnect.Services.Models.v1.Routes.Route;

namespace FitConnect;

internal class Router : IRouter {
    private readonly FitConnectEnvironment _environment;
    private readonly ILogger? _logger;
    private readonly IRouteService _routeService;
    private readonly ISelfServicePortalService _selfServicePortalService;


    public Router(FitConnectEnvironment environment, ILogger? logger = null) {
        _environment = environment;
        _logger = logger;
        _routeService = new RouteService(environment.RoutingUrl, "v1", logger);
        _selfServicePortalService = new SelfServicePortalService(environment.SspUrl, "v1", logger);
    }

    public async Task<List<Route>> FindDestinationsAsync(string leiaKey, string? ags = null,
        string? ars = null,
        string? areaId = null) {
        var routes = await _routeService.GetDestinationIdAsync(leiaKey, ags, ars, areaId);

        foreach (var route in routes) {
            _logger?.LogInformation("Testing destination {DestinationId}", route.DestinationId);
            var verifyJwt = await VerifyDestinationSignature(route);
            if (!verifyJwt) throw new Exception("Invalid destination signature");

            verifyJwt &= await VerifyDestinationParametersSignature(route);

            if (!verifyJwt) throw new Exception("Invalid destination parameter signature");

            verifyJwt &= VerifySubmissionHost(route);

            if (!verifyJwt)
                throw new Exception(
                    "SubmissionHost does not match DestinationParameters SubmissionUrl");
        }

        return routes;
    }


    /// <summary>
    ///     Finding Areas
    /// </summary>
    /// <param name="filter"></param>
    /// <param name="totalCount"></param>
    /// <param name="offset"></param>
    /// <param name="limit"></param>
    /// <returns></returns>
    public async Task<IEnumerable<Area>> GetAreas(List<string> filter, int offset = 0,
        int limit = 100) {
        var dto = await _routeService.GetAreas(filter, offset, limit);
        // totalCount = dto?.TotalCount ?? 0;
        return dto?.Areas ?? new List<Area>();
    }

    public async Task<IEnumerable<Area>> GetAreas(string filter, int offset = 0,
        int limit = 100) => await GetAreas(new List<string> { filter }, offset, limit);

    private bool VerifySubmissionHost(Route route) {
        var signature = new JsonWebToken(route.DestinationSignature);
        var payload =
            JsonConvert.DeserializeObject<dynamic>(
                Base64UrlEncoder.Decode(signature.EncodedPayload));

        string? submissionHost = payload?.submissionHost;
        if (submissionHost == null) return false;

        var destinationUri = new Uri(route.DestinationParameters.SubmissionUrl);

        return destinationUri.Host == submissionHost;
    }


    private async Task<bool> VerifyDestinationParametersSignature(Route route) {
        // Get Key from SubmissionAPI
        var submissionKey =
            await GetSubmissionServiceValidationJwk(route.DestinationParameters.SubmissionUrl);

        var parameterJson = JsonConvert.SerializeObject(route.DestinationParameters,
            new JsonSerializerSettings {
                NullValueHandling = NullValueHandling.Ignore,
                Formatting = Formatting.None,
                ContractResolver = new OrderedContractResolver()
            });

        var encodedParameter = Base64Url.Encode(Encoding.UTF8.GetBytes(parameterJson));

        var signature =
            route.DestinationParametersSignature.Replace("..", $".{encodedParameter}.");

        // KID to check is in signature header kid
        var header = JsonConvert.DeserializeObject<dynamic>(
            Base64UrlEncoder.Decode(route.DestinationParametersSignature.Split('.')[0])
        );

        var kid = (string)header?.kid!;

        _logger?.LogInformation("Testing with kid: {Kid}", kid);
        return FitEncryption.VerifyJwt(signature,
            new JsonWebKeySet(submissionKey).Keys.First(k => k.Kid == kid), _logger);
    }

    private async Task<bool> VerifyDestinationSignature(Route route) {
        var validation = await _selfServicePortalService.GetValidationJwk();
        var token = new JsonWebToken(route.DestinationSignature);
        var key = validation.Keys.First(k => k.Kid == token.Kid);
        var verifyJwt =
            FitEncryption.VerifyJwt(route.DestinationSignature, key, _logger);
        return verifyJwt;
    }

    /// <summary>
    /// </summary>
    /// <returns></returns>
    public static async Task<string> GetSubmissionServiceValidationJwk(string baseUrl) {
        var client = new HttpClient {
            BaseAddress = new Uri(baseUrl)
        };

        var result = await client.GetAsync("/.well-known/jwks.json");
        return await result.Content.ReadAsStringAsync();
    }
}

public class OrderedContractResolver : DefaultContractResolver {
    protected override IList<JsonProperty> CreateProperties(
        Type type, MemberSerialization memberSerialization) {
        NamingStrategy = new CamelCaseNamingStrategy();
        return base.CreateProperties(type, memberSerialization).OrderBy(p => p.PropertyName)
            .ToList();
    }
}