using System.Collections;
using System.ComponentModel;
using song_of_the_day;
using System.DirectoryServices.Protocols;
using System.Linq;

public class LdapIntegration
{
    public static LdapIntegration? Instance;

    private readonly string[] attributesToQuery = new string[]
        {
            "uid",
            "givenName",
            "sn",
            "mail"
        };

    public LdapIntegration(string uri, int port, string adminBind, string adminPass)
    {
        this.Uri = uri;
        this.Port = port;
        this.AdminBind = adminBind;
        this.AdminPass = adminPass;
    }

    private string Uri { get; set; }

    private int Port { get; set; }

    private string AdminBind { get; set; }

    private string AdminPass { get; set; }

    public bool TestLogin(string username, string password)
    {
        try
        {
            var userList = this.SearchInADAsUser(
                AppConfiguration.Instance.LDAPConfig.LDAPQueryBase,
                $"(uid={username})",
                SearchScope.Subtree,
                username,
                password);
        }
        catch (LdapException ex)
        {
            if (ex.Message.Contains("credential is invalid"))
            {
                return false;
            }
            throw;
        }
        return true;
    }

    public List<LdapUser> SearchInAD(
        string targetOU,
        string query,
        SearchScope scope
        )
    {
        // search as admin
        return this.SearchInADAsUser(targetOU, query, scope, this.AdminBind, this.AdminPass);
    }

    public List<LdapUser> SearchInADAsUser(
        string targetOU,
        string query,
        SearchScope scope,
        string userName,
        string userPass
        )
    {
        // on Windows the authentication type is Negotiate, so there is no need to prepend
        // AD user login with domain. On other platforms at the moment only
        // Basic authentication is supported
        var authType = AuthType.Basic;

        var user = userName.StartsWith("cn=") || userName.StartsWith("uid=") ? userName : "uid=" + userName + "," + AppConfiguration.Instance.LDAPConfig.LDAPUserQueryBase;

        //var connection = new LdapConnection(ldapServer)
        var connection = new LdapConnection(
            new LdapDirectoryIdentifier(this.Uri, this.Port)
            )
        {
            AuthType = authType,
            Credential = new(user, userPass)
        };

        // the default one is v2 (at least in that version), and it is unknown if v3
        // is actually needed, but at least Synology LDAP works only with v3,
        // and since our Exchange doesn't complain, let it be v3
        connection.SessionOptions.ProtocolVersion = 3;

        // this is for connecting via LDAPS (636 port). It should be working,
        // according to https://github.com/dotnet/runtime/issues/43890,
        // but it doesn't (at least with Synology DSM LDAP), although perhaps
        // for a different reason
        //connection.SessionOptions.SecureSocketLayer = true;

        connection.Bind();

        var request = new SearchRequest(targetOU, query, scope, attributesToQuery);

        var response = (System.DirectoryServices.Protocols.SearchResponse)connection.SendRequest(request);

        var userList = new List<LdapUser>();

        foreach (SearchResultEntry result in response.Entries)
        {
            userList.Add(new LdapUser()
            {
                UserId = result.Attributes["uid"][0].ToString(),
                FirstName = result.Attributes["givenName"][0].ToString(),
                LastName = result.Attributes["sn"][0].ToString(),
                Email = result.Attributes["mail"][0].ToString(),
            });
        }

        connection.Dispose();

        return userList;
    }
}