using System.Globalization;
using System.Text;
using Digdir.Domain.Dialogporten.Application.Common.Extensions;
using Digdir.Domain.Dialogporten.Application.Externals.AltinnAuthorization;
using Digdir.Domain.Dialogporten.Application.Externals.Presentation;
using Digdir.Domain.Dialogporten.Domain.Dialogs.Entities;
using Digdir.Domain.Dialogporten.Domain.Parties;
using Microsoft.Extensions.Options;

namespace Digdir.Domain.Dialogporten.Application.Common;

public interface IDialogTokenGenerator
{
    string GetDialogToken(DialogEntity dialog, DialogDetailsAuthorizationResult authorizationResult, string issuerVersion);
}

internal sealed class DialogTokenGenerator : IDialogTokenGenerator
{
    private readonly ApplicationSettings _applicationSettings;
    private readonly IUser _user;
    private readonly IClock _clock;
    private readonly ICompactJwsGenerator _compactJwsGenerator;

    // Keep the lifetime semi-short to reduce the risk of token misuse
    // after rights revocation, whilst still making it possible for the
    // user to idle a reasonable amount of time before committing to an action.
    //
    // End user systems should make sure to re-request the dialog, upon
    // which a new token will be issued based on current authorization data.
    private readonly TimeSpan _tokenLifetime = TimeSpan.FromMinutes(10);

    public DialogTokenGenerator(
        IOptions<ApplicationSettings> applicationSettings,
        IUser user,
        IClock clock,
        ICompactJwsGenerator compactJwsGenerator)
    {
        _applicationSettings = applicationSettings.Value ?? throw new ArgumentNullException(nameof(applicationSettings));
        _user = user ?? throw new ArgumentNullException(nameof(user));
        _clock = clock ?? throw new ArgumentNullException(nameof(clock));
        _compactJwsGenerator = compactJwsGenerator ?? throw new ArgumentNullException(nameof(compactJwsGenerator));
    }

    public string GetDialogToken(DialogEntity dialog, DialogDetailsAuthorizationResult authorizationResult, string issuerVersion)
    {
        var claimsPrincipal = _user.GetPrincipal();
        var now = _clock.UtcNowOffset.ToUnixTimeSeconds();

        var claims = new Dictionary<string, object?>
        {
            { DialogTokenClaimTypes.JwtId, Guid.NewGuid() },
            { DialogTokenClaimTypes.AuthenticatedParty, GetAuthenticatedParty() },
            { DialogTokenClaimTypes.AuthenticationLevel,
                claimsPrincipal.TryGetAuthenticationLevel(out var authenticationLevel)
                ? authenticationLevel.Value
                : 0 },
            { DialogTokenClaimTypes.DialogParty, dialog.Party },
            { DialogTokenClaimTypes.ServiceResource, dialog.ServiceResource },
            { DialogTokenClaimTypes.DialogId, dialog.Id },
            { DialogTokenClaimTypes.Actions, GetAuthorizedActions(authorizationResult) },
            { DialogTokenClaimTypes.Issuer, _applicationSettings.Dialogporten.BaseUri.AbsoluteUri.TrimEnd('/') + issuerVersion },
            { DialogTokenClaimTypes.IssuedAt, now },
            { DialogTokenClaimTypes.NotBefore, now },
            { DialogTokenClaimTypes.Expires, now + (long)_tokenLifetime.TotalSeconds }
        };

        if (claimsPrincipal.TryGetSupplierOrgNumber(out var supplierOrgNumber))
        {
            claims.Add(DialogTokenClaimTypes.SupplierParty, NorwegianOrganizationIdentifier.PrefixWithSeparator + supplierOrgNumber);
        }

        return _compactJwsGenerator.GetCompactJws(claims);
    }

    private static string GetAuthorizedActions(DialogDetailsAuthorizationResult authorizationResult)
    {
        if (authorizationResult.AuthorizedAltinnActions.Count == 0)
        {
            return string.Empty;
        }

        var actions = new StringBuilder();
        foreach (var (action, resource) in authorizationResult.AuthorizedAltinnActions)
        {
            actions.Append(action);
            if (resource != Authorization.Constants.MainResource)
            {
                actions.Append(CultureInfo.InvariantCulture, $",{resource}");
            }

            actions.Append(';');
        }

        // Remove trailing semicolon
        actions.Remove(actions.Length - 1, 1);

        return actions.ToString();
    }

    private string GetAuthenticatedParty()
    {
        if (_user.TryGetPid(out var pid))
        {
            return NorwegianPersonIdentifier.PrefixWithSeparator + pid;
        }

        if (_user.TryGetOrganizationNumber(out var orgNumber))
        {
            return NorwegianOrganizationIdentifier.PrefixWithSeparator + orgNumber;
        }

        throw new InvalidOperationException("User must have either pid or org number");
    }
}

public static class DialogTokenClaimTypes
{
    public const string JwtId = "jti";
    public const string Issuer = "iss";
    public const string IssuedAt = "iat";
    public const string NotBefore = "nbf";
    public const string Expires = "exp";
    public const string AuthenticationLevel = "l";
    public const string AuthenticatedParty = "c";
    public const string DialogParty = "p";
    public const string SupplierParty = "u";
    public const string ServiceResource = "s";
    public const string DialogId = "i";
    public const string Actions = "a";
}