/*******************************************************************************
 * Copyright (c) 2026, Jan Koester jan.koester@gmx.net
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 * Redistributions of source code must retain the above copyright
 *      notice, this list of conditions and the following disclaimer.
 * Redistributions in binary form must reproduce the above copyright
 *      notice, this list of conditions and the following disclaimer in the
 *      documentation and/or other materials provided with the distribution.
 * Neither the name of the <organization> nor the
 *      names of its contributors may be used to endorse or promote products
 *      derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *******************************************************************************/

#pragma once

#include <string>
#include <vector>
#include <map>
#include <mutex>
#include <memory>
#include <cstdint>

#include <uuidp.h>
#include <netplus/socket.h>
#include <netplus/connection.h>
#include <netplus/eventapi.h>

#include "backend.h"
#include "plugin.h"

#ifdef Windows
#define VISIBILITY __declspec(dllexport)
#else
#define VISIBILITY __attribute__((visibility("default")))
#endif

namespace authdb {

    /***************************************************************************
     * LDAP Protocol Constants (RFC 4511)
     **************************************************************************/

    /* LDAP Application Tags (constructed unless noted) */
    enum LdapAppTag : uint8_t {
        LDAP_BIND_REQUEST        = 0,   /* [APPLICATION 0]  */
        LDAP_BIND_RESPONSE       = 1,   /* [APPLICATION 1]  */
        LDAP_UNBIND_REQUEST      = 2,   /* [APPLICATION 2]  primitive */
        LDAP_SEARCH_REQUEST      = 3,   /* [APPLICATION 3]  */
        LDAP_SEARCH_RESULT_ENTRY = 4,   /* [APPLICATION 4]  */
        LDAP_SEARCH_RESULT_DONE  = 5,   /* [APPLICATION 5]  */
        LDAP_MODIFY_REQUEST      = 6,   /* [APPLICATION 6]  */
        LDAP_MODIFY_RESPONSE     = 7,   /* [APPLICATION 7]  */
        LDAP_ADD_REQUEST         = 8,   /* [APPLICATION 8]  */
        LDAP_ADD_RESPONSE        = 9,   /* [APPLICATION 9]  */
        LDAP_DEL_REQUEST         = 10,  /* [APPLICATION 10] primitive */
        LDAP_DEL_RESPONSE        = 11,  /* [APPLICATION 11] */
        LDAP_MODRDN_REQUEST      = 12,  /* [APPLICATION 12] */
        LDAP_MODRDN_RESPONSE     = 13,  /* [APPLICATION 13] */
        LDAP_COMPARE_REQUEST     = 14,  /* [APPLICATION 14] */
        LDAP_COMPARE_RESPONSE    = 15,  /* [APPLICATION 15] */
        LDAP_ABANDON_REQUEST     = 16,  /* [APPLICATION 16] primitive */
        LDAP_EXTENDED_REQUEST    = 23,  /* [APPLICATION 23] */
        LDAP_EXTENDED_RESPONSE   = 24   /* [APPLICATION 24] */
    };

    /* LDAP Result Codes (RFC 4511 §4.1.9) */
    enum LdapResultCode : int32_t {
        LDAP_SUCCESS                 = 0,
        LDAP_OPERATIONS_ERROR        = 1,
        LDAP_PROTOCOL_ERROR          = 2,
        LDAP_TIME_LIMIT_EXCEEDED     = 3,
        LDAP_SIZE_LIMIT_EXCEEDED     = 4,
        LDAP_AUTH_METHOD_NOT_SUPPORTED = 7,
        LDAP_STRONGER_AUTH_REQUIRED  = 8,
        LDAP_NO_SUCH_OBJECT          = 32,
        LDAP_INVALID_DN_SYNTAX       = 34,
        LDAP_INVALID_CREDENTIALS     = 49,
        LDAP_INSUFFICIENT_ACCESS     = 50,
        LDAP_BUSY                    = 51,
        LDAP_UNAVAILABLE             = 52,
        LDAP_UNWILLING_TO_PERFORM    = 53,
        LDAP_OTHER                   = 80
    };

    /* LDAP Search Scope (RFC 4511 §4.5.1) */
    enum LdapSearchScope : uint8_t {
        LDAP_SCOPE_BASE     = 0,
        LDAP_SCOPE_ONELEVEL = 1,
        LDAP_SCOPE_SUBTREE  = 2
    };

    /* LDAP Search DerefAliases */
    enum LdapDerefAliases : uint8_t {
        LDAP_DEREF_NEVER       = 0,
        LDAP_DEREF_SEARCHING   = 1,
        LDAP_DEREF_FINDING     = 2,
        LDAP_DEREF_ALWAYS      = 3
    };

    /* LDAP Filter Choice Tags */
    enum LdapFilterTag : uint8_t {
        LDAP_FILTER_AND          = 0,   /* [0] SET OF Filter       */
        LDAP_FILTER_OR           = 1,   /* [1] SET OF Filter       */
        LDAP_FILTER_NOT          = 2,   /* [2] Filter              */
        LDAP_FILTER_EQUALITY     = 3,   /* [3] AttributeValueAssertion */
        LDAP_FILTER_SUBSTRINGS   = 4,   /* [4] SubstringFilter     */
        LDAP_FILTER_GE           = 5,   /* [5] AttributeValueAssertion */
        LDAP_FILTER_LE           = 6,   /* [6] AttributeValueAssertion */
        LDAP_FILTER_PRESENT      = 7,   /* [7] AttributeDescription */
        LDAP_FILTER_APPROX       = 8,   /* [8] AttributeValueAssertion */
        LDAP_FILTER_EXTENSIBLE   = 9    /* [9] MatchingRuleAssertion */
    };

    /***************************************************************************
     * BER (Basic Encoding Rules) Codec for LDAP
     **************************************************************************/
    class VISIBILITY LdapBer {
    public:
        /* BER tag constants */
        static constexpr uint8_t TAG_BOOLEAN       = 0x01;
        static constexpr uint8_t TAG_INTEGER       = 0x02;
        static constexpr uint8_t TAG_OCTET_STRING  = 0x04;
        static constexpr uint8_t TAG_NULL          = 0x05;
        static constexpr uint8_t TAG_ENUMERATED    = 0x0A;
        static constexpr uint8_t TAG_SEQUENCE      = 0x30;
        static constexpr uint8_t TAG_SET           = 0x31;
        static constexpr uint8_t CLASS_CONTEXT     = 0x80;
        static constexpr uint8_t CLASS_APPLICATION = 0x40;
        static constexpr uint8_t CONSTRUCTED       = 0x20;

        /* ---- Encoding ---- */
        static void encodeLength(std::vector<uint8_t> &buf, size_t len);
        static void encodeTLV(std::vector<uint8_t> &buf, uint8_t tag,
                              const std::vector<uint8_t> &value);
        static std::vector<uint8_t> encodeInteger(int32_t val);
        static std::vector<uint8_t> encodeEnumerated(int32_t val);
        static std::vector<uint8_t> encodeBoolean(bool val);
        static std::vector<uint8_t> encodeOctetString(const std::string &s);
        static std::vector<uint8_t> encodeOctetString(const std::vector<uint8_t> &data);
        static std::vector<uint8_t> encodeSequence(const std::vector<uint8_t> &contents);
        static std::vector<uint8_t> encodeSet(const std::vector<uint8_t> &contents);

        /* ---- Decoding ---- */
        static bool parseTLV(const uint8_t *data, size_t len, size_t &off,
                             uint8_t &tag, const uint8_t *&content, size_t &contentLen);
        static bool parseInteger(const uint8_t *data, size_t len, int32_t &val);
        static bool parseOctetString(const uint8_t *data, size_t len, std::string &out);
        static bool parseBoolean(const uint8_t *data, size_t len, bool &val);
        static bool parseEnumerated(const uint8_t *data, size_t len, int32_t &val);
    };

    /***************************************************************************
     * LDAP Attribute (name + set of values)
     **************************************************************************/
    struct VISIBILITY LdapAttribute {
        std::string              type;
        std::vector<std::string> values;
    };

    /***************************************************************************
     * LDAP Entry (DN + list of attributes)
     **************************************************************************/
    struct VISIBILITY LdapEntry {
        std::string                dn;
        std::vector<LdapAttribute> attributes;

        void addAttribute(const std::string &type, const std::string &value);
        void addAttribute(const std::string &type, const std::vector<std::string> &values);
    };

    /***************************************************************************
     * LDAP Search Filter (parsed from BER)
     **************************************************************************/
    struct VISIBILITY LdapFilter {
        LdapFilterTag                       tag;
        std::string                         attrType;    /* for equality/present/etc */
        std::string                         attrValue;   /* for equality/etc */
        std::vector<std::shared_ptr<LdapFilter>> children; /* for AND/OR/NOT */

        LdapFilter();

        bool matches(const LdapEntry &entry) const;
    };

    /***************************************************************************
     * Per-connection LDAP session state
     **************************************************************************/
    struct VISIBILITY LdapSession {
        bool         authenticated = false;
        std::string  bindDN;         /* DN of the authenticated principal */
        uuid::uuid   userUid;        /* UUID if bound as a user          */
        std::string  realm;          /* domain realm for the session     */
    };

    /***************************************************************************
     * Active Directory Plugin
     *
     * Implements an LDAP server that exposes the authdb backend as an
     * Active Directory-compatible directory.  Supports:
     *   - Simple bind (password authentication via authdb)
     *   - SASL GSSAPI bind (Kerberos, depends on krb5 plugin)
     *   - Search for users, groups, computers, services
     *   - AD-style schema (objectClass, sAMAccountName, etc.)
     *   - Multiple domains mapped to LDAP base DNs
     *
     * Domain "example.com" → base DN "DC=example,DC=com"
     *
     * Directory tree layout:
     *   DC=example,DC=com
     *   ├── CN=Users
     *   │   └── CN=<username>
     *   ├── CN=Groups
     *   │   └── CN=<groupname>
     *   ├── CN=Computers
     *   │   └── CN=<clientname>
     *   └── CN=Services
     *       └── CN=<servicename>/<hostname>
     **************************************************************************/
    class VISIBILITY ActiveDirectory : public netplus::eventapi, public AdPlugin {
    public:
        ActiveDirectory(AuthBackend &adminBackend,
                        const std::string &bindAddr = "0.0.0.0",
                        int port = 389, int maxconn = 128);
        ~ActiveDirectory() override;

        /* Plugin interface */
        const char *name() const override { return "ad"; }
        void init() override;
        void run() override;

        /* AdPlugin interface */
        std::string baseDN(const std::string &domain) const override;

        /* netplus::eventapi overrides */
        void RequestEvent(netplus::con &curcon, const int tid, ULONG_PTR args) override;
        void ResponseEvent(netplus::con &curcon, const int tid, ULONG_PTR args) override;
        void ConnectEvent(netplus::con &curcon, const int tid, ULONG_PTR args) override;
        void DisconnectEvent(netplus::con &curcon, const int tid, ULONG_PTR args) override;
        void CreateConnection(std::shared_ptr<netplus::con> &res) override;

    private:
        /* Convert domain name to LDAP base DN: "example.com" → "DC=example,DC=com" */
        static std::string domainToBaseDN(const std::string &domain);

        /* Convert DN components to lowercase for case-insensitive comparison */
        static std::string normalizeDN(const std::string &dn);

        /* Determine which domain backend a base DN refers to */
        AuthBackend *resolveBackendFromDN(const std::string &dn, std::string &domainName);

        /* Load all domains from the admin backend */
        void loadDomains();

        /* ---- LDAP Message Handling ---- */
        std::vector<uint8_t> handleMessage(const uint8_t *data, size_t len,
                                           LdapSession &session);
        std::vector<uint8_t> handleBindRequest(const uint8_t *content, size_t len,
                                               int32_t msgId, LdapSession &session);
        std::vector<uint8_t> handleSearchRequest(const uint8_t *content, size_t len,
                                                 int32_t msgId, LdapSession &session);
        std::vector<uint8_t> handleUnbindRequest(int32_t msgId, LdapSession &session);

        /* ---- LDAP Response Builders ---- */
        std::vector<uint8_t> buildLdapMessage(int32_t msgId, uint8_t appTag,
                                              const std::vector<uint8_t> &protocolOp);
        std::vector<uint8_t> buildLdapResult(LdapResultCode code,
                                             const std::string &matchedDN,
                                             const std::string &diagnosticMsg);
        std::vector<uint8_t> buildSearchResultEntry(int32_t msgId,
                                                    const LdapEntry &entry);
        std::vector<uint8_t> buildSearchResultDone(int32_t msgId,
                                                   LdapResultCode code,
                                                   const std::string &msg = "");

        /* ---- Directory Enumeration ---- */
        void enumerateUsers(AuthBackend &backend, const std::string &baseDN,
                            const LdapFilter &filter,
                            std::vector<LdapEntry> &results);
        void enumerateGroups(AuthBackend &backend, const std::string &baseDN,
                             const LdapFilter &filter,
                             std::vector<LdapEntry> &results);
        void enumerateClients(AuthBackend &backend, const std::string &baseDN,
                              const LdapFilter &filter,
                              std::vector<LdapEntry> &results);
        void enumerateServices(AuthBackend &backend, const std::string &baseDN,
                               const LdapFilter &filter,
                               std::vector<LdapEntry> &results);
        void enumerateContainers(const std::string &baseDN,
                                 std::vector<LdapEntry> &results);

        /* Build the RootDSE entry */
        LdapEntry buildRootDSE();

        /* ---- Filter Parsing ---- */
        std::shared_ptr<LdapFilter> parseFilter(const uint8_t *data, size_t len);

        /* ---- Simple Bind Authentication ---- */
        bool authenticateSimple(const std::string &bindDN, const std::string &password,
                                LdapSession &session);

        /* ---- Members ---- */
        AuthBackend                           &_AdminBackend;
        std::string                            _BindAddr;
        int                                    _Port;
        int                                    _MaxConn;

        struct DomainInfo {
            uuid::uuid   domainId;
            std::string  domainName;
            std::string  baseDN;
            int          storageType;
            std::string  storageOptions;
        };
        std::vector<DomainInfo>                _Domains;

        /* Session map: connection pointer → session state */
        std::map<netplus::con*, LdapSession>   _Sessions;
        std::mutex                             _Lock;

        std::vector<netplus::socket*>          _ServerSockets;
        void                                  *_Event;
    };

}; /* namespace authdb */
