using Ablera.Serdica.Common.Tools.Models.Config;
using Ablera.Serdica.Common.Tools;
using Ablera.Serdica.DBModels.Serdica;
using Ablera.Serdica.Authority.Plugins.Base.Contracts;
using Ablera.Serdica.Authority.Plugins.Base.Models;
using Microsoft.AspNetCore.Identity;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Security.Claims;
using System.Text;
using System.Threading.Tasks;
using Ablera.Serdica.Common.Tools.Expressions.Models;
using Novell.Directory.Ldap;
using Ablera.Serdica.Extensions.Novell.Directory.Ldap.Contracts;
using Ablera.Serdica.Extensions.Novell.Directory.Ldap.Attributes;
using Ablera.Serdica.Extensions.Novell.Directory.Ldap.Models;

namespace Ablera.Serdica.Authority.Plugin.Ldap;

/// <summary>
/// Thin façade that lets the generic pipeline work with <see cref="UserAccount"/> while delegating the real
/// work to an <see cref="IUserManager{OpenLdapAccount,string}"/> that talks to the directory.
/// </summary>
public class IdentityManagementFacade
        : IUserManagementFacade<IdentityUser<string>>
{
    private readonly LdapSettingsProvider ldapSettingsProvider;
    private readonly LdapIdentityFacade userRepository;

    public IdentityManagementFacade(
        ILogger<GenericJsonSettingsProvider<Serdica.Extensions.Novell.Directory.Ldap.Models.LdapSettings[]>> logger,
        IOptions<JsonFileSettingsConfig> options,
        IUsernameNormalizer usernameNormalizer,
        IEmailNormalizer emailNormalizer,
        ILogger<LdapIdentityFacade> logger2,
        ILogger<LdapIdentityFacade> logger3)
    {
        ldapSettingsProvider = new LdapSettingsProvider(
            logger,
            options); 
        userRepository = new LdapIdentityFacade(
            logger3,
            logger2,
            ldapSettingsProvider,
            emailNormalizer,
            usernameNormalizer);
    }

    #region IAuthService

    public async Task<AuthenticationResult> AuthenticateAsync(IdentityUser<string> identityUser,
                                                              string password,
                                                              bool lockoutOnFailure = false,
                                                              CancellationToken ct = default)
    {
        var ldap = await ResolveAsync(identityUser, ct);
        if (ldap is null)
            return AuthenticationResult.Fail("USER_NOT_FOUND");

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

    #endregion

    #region IUserStore – best‑effort mapping

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

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

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

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

        var ldap = new LdapIdentity 
        { 
            Identity = identityUser, 
            Username = identityUser.UserName,
            Email = identityUser.Email,
            LdapSettings = ldapSettingsProvider.Settings.First()
        };

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

    public async Task<OperationResult> UpdateAsync(IdentityUser<string> identityUser, CancellationToken ct = default)
    {
        if (identityUser == null) return OperationResult.Fail("NULL_USER");
        var ldapIdentity = await ResolveAsync(identityUser, ct);
        if (ldapIdentity == null) return OperationResult.Fail("USER_NOT_FOUND");

        ldapIdentity.Username = identityUser.UserName;
        ldapIdentity.Email = identityUser.Email;

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

    #endregion

    #region Claim helpers

    public async Task<IReadOnlyCollection<Claim>> GetBaseClaimsAsync(IdentityUser<string> identityUser, CancellationToken ct = default)
    {
        var ldap = await ResolveAsync(identityUser, 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 operations – delegated

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

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

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

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

    #endregion

    #region Helpers

    private async Task<LdapIdentity?> 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;
    }

    #endregion
}
