/*******************************************************************************
 * Copyright (c) 2025, 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.
 *******************************************************************************/

#include <vector>
#include <string>
#include <memory>
#include <mutex>
#include <chrono>
#include <utility>
#include <unordered_map>
#include <uuidp.h>

#include "authdb.h"
#include "group.h"
#include "index.h"

#pragma once

namespace authdb {
    class Cluster;
}

namespace authdb {

    namespace client {
        class Client;
    };

    class SessionData {
    public:
        bool isMemberOf(const uuid::uuid &gid) const;

        void getSid(uuid::uuid &dest) const;
        void getUid(uuid::uuid &dest) const;
        void getDid(uuid::uuid &dest) const;

        const char *getUsername() const;

        const SessionData *next() const;

        SessionData();
        SessionData(const SessionData &src);
        SessionData(const SessionData *src);
        SessionData(uuid::uuid sessionid,uuid::uuid userid,uuid::uuid domainid,std::vector<uuid::uuid> &members);
        ~SessionData();

        void clear();
        bool empty() const;

        bool checkGPO(uuid::uuid gpoid) const;

        void getMembers(std::vector<uuid::uuid> &out) const;
        void getGPOResults(std::vector<std::pair<uuid::uuid, bool>> &out) const;
        void setGPOResults(const std::vector<std::pair<uuid::uuid, bool>> &results);

    private:

        class GPOResult{
        public:
            GPOResult();
            ~GPOResult();
            uuid::uuid GPOId;
            bool       GPORes;
            GPOResult *next;
        } _GPOResult;

        uuid::uuid                _uid;
        uuid::uuid                _sid;
        uuid::uuid                _did;

        std::vector<uuid::uuid>   _members;

        std::string              _username;

        std::chrono::steady_clock::time_point _created;
        std::chrono::steady_clock::time_point _lastClusterSync;

        SessionData        *_next;
        friend class Session;
        friend class LocalSession;
        friend class ClusterSession;
        friend class client::Client;
        friend SessionData *createSessionData(AuthBackend &backend, uuid::uuid domainid, uuid::uuid userid);
        friend void reloadSessionData(AuthBackend &backend, SessionData *cursess);
    };

    // Session listing info (used by listSessions)
    struct SessionInfo {
        uuid::uuid sid, uid, did;
        std::string username;
    };

    // Abstract session store interface
    class Session{
    public:
        Session() = default;
        virtual ~Session() = default;

        virtual const SessionData *addSession(AuthBackend &backend,uuid::uuid domainid,uuid::uuid userid) = 0;
        virtual void               relSession(AuthBackend &backend,uuid::uuid sessionid) = 0;
        virtual const SessionData *getSession(uuid::uuid sessionid) = 0;
        virtual void               remSession(uuid::uuid sessionid) = 0;
        virtual void               listSessions(std::vector<SessionInfo> &out) = 0;
        virtual void               clear() = 0;

        Session(const Session&) = delete;
        Session& operator=(const Session&) = delete;
    };

    // Single-node session store: in-memory linked list
    class LocalSession : public Session {
    public:
        LocalSession();
        ~LocalSession() override;

        const SessionData *addSession(AuthBackend &backend,uuid::uuid domainid,uuid::uuid userid) override;
        void               relSession(AuthBackend &backend,uuid::uuid sessionid) override;
        const SessionData *getSession(uuid::uuid sessionid) override;
        void               remSession(uuid::uuid sessionid) override;
        void               listSessions(std::vector<SessionInfo> &out) override;
        void               clear() override;

    protected:
        SessionData *findLocal(uuid::uuid sessionid);
        void         appendLocal(SessionData *sess);
        void         removeLocal(uuid::uuid sessionid);
        void         cleanupExpired();

        std::unordered_map<uuid::uuid, SessionData*, UuidHash, UuidEqual> _sessions;
        std::mutex     _lock;
    };

    // Cluster session store: uses cluster session_store_ as single source of truth
    class ClusterSession : public LocalSession {
    public:
        ClusterSession();
        ~ClusterSession() override;

        const SessionData *addSession(AuthBackend &backend,uuid::uuid domainid,uuid::uuid userid) override;
        void               relSession(AuthBackend &backend,uuid::uuid sessionid) override;
        const SessionData *getSession(uuid::uuid sessionid) override;
        void               remSession(uuid::uuid sessionid) override;
        void               listSessions(std::vector<SessionInfo> &out) override;
    };
};
