using Ablera.Serdica.Authority.Plugins.Base.Contracts;
using Ablera.Serdica.Authority.Plugins.Base.Models;
using Ablera.Serdica.Authority.Plugin.Ldap.Models;
using Microsoft.Extensions.Logging;
using System.Security.Claims;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Identity;
using Ablera.Serdica.Common.Tools.Extensions;
using Ablera.Serdica.Authority.Plugin.Ldap;

namespace Ablera.Serdica.Authority.Plugin.Bulstrad;

/// <summary>
/// Adapter exposing Bulstrad‑specific LDAP manager as <see cref="IUserManagementFacade{UserAccount}"/>.
/// </summary>
public class IdentityManagementFacade(BulstradAdIdentityFacade userRepository, BulstradAsLdapSettingsProvider settingsProvider) : IUserManagementFacade<IdentityUser<string>>
{
    #region Authentication

    public async Task<AuthenticationResult> AuthenticateAsync(IdentityUser<string> user, string password, bool lockoutOnFailure = false, CancellationToken ct = default)
    {
        var ldap = await ResolveAsync(user, ct);
        if (ldap == null) return AuthenticationResult.Fail(AuthenticationCode.AccountIsNotFound.ToScreamingSnakeCase());
        return await userRepository.AuthenticateAsync(ldap, password, lockoutOnFailure, ct);
    }

    #endregion

    #region Store lookups

    public async Task<IdentityUser<string>?> FindByEmailAsync(string email, CancellationToken ct = default)
        => Map(await userRepository.FindByEmailAsync(email, ct));

    public async Task<IdentityUser<string>?> FindByNameAsync(string username, CancellationToken ct = default)
        => Map(await userRepository.FindByNameAsync(username, ct));

    public async Task<IdentityUser<string>?> FindByIdAsync(string id, CancellationToken ct = default)
        => Map(await userRepository.FindByIdAsync(id, ct));

    public async Task<OperationResult> CreateAsync(IdentityUser<string> user, string password, CancellationToken ct = default)
    {
        if (user == null) return OperationResult.Fail("NULL_USER");

        var ldap = new BulstradAdIdentity 
        { 
            Username = user.UserName,
            Email = user.Email ?? user.UserName,
            ObjectClasses = ["top", "person", "organizationalPerson", "inetorgperson"],
            Identity = user, 
            LdapSettings = settingsProvider.Settings.First() 
        };

        return await userRepository.CreateAsync(ldap, password, ct);
    }

    public async Task<OperationResult> UpdateAsync(IdentityUser<string> user, CancellationToken ct = default)
    {
        if (user == null) return OperationResult.Fail("NULL_USER");
        var ldapIdentity = await ResolveAsync(user, ct);
        if (ldapIdentity == null) return OperationResult.Fail("USER_NOT_FOUND");
        
        ldapIdentity.Username = user.UserName;
        ldapIdentity.Email = user.Email;
        ldapIdentity.ObjectClasses =  [ "top", "person", "organizationalPerson", "inetorgperson"];
        ldapIdentity.Identity = user;
        ldapIdentity.LdapSettings = settingsProvider.Settings.First();

        return await userRepository.UpdateAsync(ldapIdentity, ct);
    }
    #endregion

    #region Claims

    public async Task<IReadOnlyCollection<Claim>> GetBaseClaimsAsync(IdentityUser<string> user, CancellationToken ct = default)
    {
        var ldap = await ResolveAsync(user, ct);
        return ldap == null ? Array.Empty<Claim>() : await userRepository.GetBaseClaimsAsync(ldap, ct);
    }

    public async Task<IReadOnlyCollection<Claim>?> GetRolesClaimsAsync(IdentityUser<string> user, CancellationToken ct = default)
    {
        var ldap = await ResolveAsync(user, ct);
        return ldap == null ? null : await userRepository.GetRolesClaimsAsync(ldap, ct);
    }

    #endregion

    #region Password & lock

    public async Task<OperationResult> ChangePasswordAsync(IdentityUser<string> user, string currentPassword, string newPassword, CancellationToken ct = default)
    {
        var ldap = await ResolveAsync(user, ct);
        return ldap == null ? OperationResult.Fail("USER_NOT_FOUND") : await userRepository.ChangePasswordAsync(ldap, currentPassword, newPassword, ct);
    }

    public async Task<OperationResult> ResetPasswordAsync(IdentityUser<string> user, string token, string newPassword, CancellationToken ct = default)
    {
        var ldap = await ResolveAsync(user, ct);
        return ldap == null ? OperationResult.Fail("USER_NOT_FOUND") : await userRepository.ResetPasswordAsync(ldap, token, newPassword, ct);
    }

    public async Task<OperationResult> LockAsync(IdentityUser<string> user, CancellationToken ct = default)
    {
        var ldap = await ResolveAsync(user, ct);
        return ldap == null ? OperationResult.Fail("USER_NOT_FOUND") : await userRepository.LockAsync(ldap, ct);
    }

    public async Task<OperationResult> UnlockAsync(IdentityUser<string> user, CancellationToken ct = default)
    {
        var ldap = await ResolveAsync(user, ct);
        return ldap == null ? OperationResult.Fail("USER_NOT_FOUND") : await userRepository.UnlockAsync(ldap, ct);
    }

    #endregion

    #region Helper mapping

    private async Task<BulstradAdIdentity?> ResolveAsync(IdentityUser<string> u, CancellationToken ct)
    {
        if (u == null) return null;
        if (string.IsNullOrWhiteSpace(u.Email) == false)
        {
            var ret = await userRepository.FindByEmailAsync(u.Email, ct);
            if (ret != null)
            {
                ret.Identity = u;
                return ret;
            }
        }
        if (string.IsNullOrWhiteSpace(u.UserName) == false)
        {
            var ret = await userRepository.FindByNameAsync(u.UserName, ct);
            if (ret != null)
            {
                ret.Identity = u;
                return ret;
            }
        }
        return null;
    }

    private IdentityUser<string>? Map(BulstradAdIdentity? b)
    {
        if (b == null) return null;
        return new IdentityUser
        {
            Id = string.Empty,
            UserName = b.Username,
            Email = b.Email,
            NormalizedUserName = b.Username?.ToUpperInvariant(),
            NormalizedEmail = b.Email?.ToUpperInvariant(),
            EmailConfirmed = true
        };
    }

    #endregion
}

