using IWS5.Core.Models.Configuration;
using IWS5.LdapClient.Constants;
using IWS5.LdapClient.Interfaces;
using IWS5.LdapClient.Models;
using Microsoft.Extensions.Options;
using Serilog;
using System.DirectoryServices;
using DirectoryEntry = System.DirectoryServices.DirectoryEntry;
using System.Security.Principal;

namespace IWS5.LdapClient;

public class LdapClient(IOptions<LdapConfig> ldapConfig, ILogger logger) : ILdapClient
{
    private readonly LdapConfig _ldapConfig = ldapConfig.Value;
    
    public LdapLogInResponse LdapLogIn(LdapLogInRequest ldapLogInRequest)
    {
        var ldapResponse = new LdapLogInResponse();
        var domain = !string.IsNullOrWhiteSpace(ldapLogInRequest.Domain) ? ldapLogInRequest.Domain : _ldapConfig.SubDomain;
        var userName = $"{domain}\\{ldapLogInRequest.UserName}";
        try
        {
            var ldapPath = $"LDAP://{_ldapConfig.Server}:{_ldapConfig.Port}";

            using (DirectoryEntry entry = new(path: ldapPath, username: userName, password: ldapLogInRequest.Password))
            {
                entry.Site = null;
                entry.AuthenticationType = AuthenticationTypes.None;
                entry.UsePropertyCache = false;
                using (DirectorySearcher searcher = new DirectorySearcher(entry))
                {
                    searcher.Filter =
                    $"(&(objectClass=user)(objectCategory=person)({LdapAttributes.SAMAccountName}={ldapLogInRequest.UserName}))";

                    var propertiesToLoad = new[]
                    {
                        LdapAttributes.GivenName,
                        LdapAttributes.SurName,
                        LdapAttributes.SAMAccountName,
                        LdapAttributes.UserPrincipalName,
                        LdapAttributes.DisplayName,
                        LdapAttributes.MemberOf,
                        LdapAttributes.ObjectSid
                    };

                    searcher.PropertiesToLoad.AddRange(propertiesToLoad);

                    var result = searcher.FindOne();
                    if (result == null) return null;

                    var samAccountName = result.Properties[LdapAttributes.SAMAccountName].Count > 0 ? result.Properties[LdapAttributes.SAMAccountName][0]?.ToString() : string.Empty;
                    var userPrincipalName = result.Properties[LdapAttributes.UserPrincipalName].Count > 0 ? result.Properties[LdapAttributes.UserPrincipalName][0]?.ToString() : string.Empty;
                    var givenName = result.Properties[LdapAttributes.GivenName].Count > 0 ? result.Properties[LdapAttributes.GivenName][0]?.ToString() : string.Empty;
                    var surName = result.Properties[LdapAttributes.SurName].Count > 0 ? result.Properties[LdapAttributes.SurName][0]?.ToString() : string.Empty;
                    var displayName = result.Properties[LdapAttributes.DisplayName].Count > 0 ? result.Properties[LdapAttributes.DisplayName][0]?.ToString() : string.Empty;

                    string objectSid = string.Empty;
                    if (result.Properties[LdapAttributes.ObjectSid].Count > 0 && result.Properties[LdapAttributes.ObjectSid][0] is byte[] sidBytes)
                    {
                        objectSid = new SecurityIdentifier(sidBytes, 0).ToString();
                    }

                    var memberOf = result.Properties[LdapAttributes.MemberOf].Count > 0 ? result.Properties[LdapAttributes.MemberOf].Cast<string>().ToArray().Select(item => ParseUserGroups(item)).ToArray()
                        : [new LdapUserGroup()];
                    logger.Information($"LdapLogIn data {givenName},{surName}, {displayName}, {memberOf}, {samAccountName}, {userPrincipalName}");

                    ldapResponse = new LdapLogInResponse(givenName, surName, displayName, memberOf, samAccountName, userPrincipalName, objectSid);
                }
            }

            return ldapResponse;
        }
        catch (Exception ex)
        {
            logger.Error("LdapLogIn failed for {User}. Exception: {Exception}", userName, ex);
            return null;
        }
    }

    private LdapUserGroup ParseUserGroups(string rawValue)
    {
        var userGroup = new LdapUserGroup();
        var parts = rawValue.Split(',');

        foreach (var part in parts)
        {
            if (part.StartsWith("CN=", StringComparison.OrdinalIgnoreCase))
            {
                var value = part.Split('=')[1];
                userGroup.CommonNames = userGroup.CommonNames == null
                    ? new[] { value }
                    : userGroup.CommonNames.Append(value).ToArray();
            }
            else if (part.StartsWith("OU=", StringComparison.OrdinalIgnoreCase))
            {
                var ou = part.Split('=')[1];
                userGroup.OrganizationalUnits = userGroup.OrganizationalUnits == null
                    ? new[] { ou }
                    : userGroup.OrganizationalUnits.Append(ou).ToArray();
            }
            else if (part.StartsWith("DC=", StringComparison.OrdinalIgnoreCase))
            {
                userGroup.Domain = string.IsNullOrEmpty(userGroup.Domain)
                    ? part.Split('=')[1]
                    : $"{userGroup.Domain}.{part.Split('=')[1]}";
            }
        }

        return userGroup;
    }
}
