/*******************************************************************************
 * 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 <mutex>
#include <memory>

#include "index.h"
#include "authdb.h"
#include "backend.h"
#include "types.h"

namespace authdb {

    static std::mutex g_indexMapMutex;
    static std::unordered_map<std::string, std::unique_ptr<RecordIndex>> g_indexMap;

    static const char *nameFieldForType(int type) {
        switch (type) {
            case DataType::UserData:   return "username";
            case DataType::DomainData: return "domainname";
            case DataType::GroupData:  return "groupname";
            case DataType::ClientData: return "clientname";
            case DataType::PolicyData: return "gponame";
            case DataType::ServiceData: return "servicename";
            default: return nullptr;
        }
    }

    RecordIndex::RecordIndex() : _revision(0), _valid(false) {}

    void RecordIndex::build(AuthBackend &backend) {
        std::unique_lock<std::shared_mutex> lock(_mutex);
        _nameIndex.clear();
        _offsetIndex.clear();

        std::shared_ptr<AuthData::Record> cur = std::make_shared<AuthData::Record>();
        size_t rd = sizeof(AuthHeader), end = backend.end();

        while (rd + sizeof(AuthData::Record) <= end) {
            size_t recOffset = rd;
            backend.setPos(rd);
            backend.read(reinterpret_cast<unsigned char *>(cur.get()), sizeof(AuthData::Record));
            size_t next = backend.getPos() + cur->datasize;
            if (next < backend.getPos() || next > end) break;
            rd = next;

            if (cur->type == DataType::EmptyData)
                continue;

            uuid::uuid uid(cur->ruid);
            if (uid.empty())
                continue;

            const char *nameField = nameFieldForType(cur->type);
            if (nameField && strcmp(cur->fieldname, nameField) == 0) {
                UuidTypeKey utk{uid, cur->type};
                _offsetIndex[utk] = recOffset;

                if (cur->datasize > 0 && cur->datasize < 4096) {
                    char *tmp = new char[cur->datasize + 1];
                    backend.read(reinterpret_cast<unsigned char *>(tmp), cur->datasize);
                    tmp[cur->datasize] = '\0';
                    NameKey nk{cur->type, std::string(tmp)};
                    _nameIndex[nk] = uid;
                    delete[] tmp;
                }
            }
        }

        _revision = backend.getRevesion();
        _valid = true;
    }

    void RecordIndex::addEntry(const uuid::uuid &uid, int type, const char *name, size_t offset) {
        std::unique_lock<std::shared_mutex> lock(_mutex);
        UuidTypeKey utk{uid, type};
        _offsetIndex[utk] = offset;

        if (name) {
            NameKey nk{type, std::string(name)};
            _nameIndex[nk] = uid;
        }
    }

    void RecordIndex::removeEntry(const uuid::uuid &uid, int type) {
        std::unique_lock<std::shared_mutex> lock(_mutex);
        UuidTypeKey utk{uid, type};
        _offsetIndex.erase(utk);

        for (auto it = _nameIndex.begin(); it != _nameIndex.end(); ++it) {
            if (it->first.type == type && memcmp(it->second.value, uid.value, 16) == 0) {
                _nameIndex.erase(it);
                break;
            }
        }
    }

    bool RecordIndex::findByName(int type, const char * /*fieldname*/, const std::string &name, uuid::uuid &out_uid) const {
        std::shared_lock<std::shared_mutex> lock(_mutex);
        NameKey nk{type, name};
        auto it = _nameIndex.find(nk);
        if (it != _nameIndex.end()) {
            out_uid = it->second;
            return true;
        }
        return false;
    }

    bool RecordIndex::findByUuid(int type, const uuid::uuid &uid) const {
        std::shared_lock<std::shared_mutex> lock(_mutex);
        UuidTypeKey utk;
        utk.uid = uid;
        utk.type = type;
        return _offsetIndex.find(utk) != _offsetIndex.end();
    }

    size_t RecordIndex::findOffset(const uuid::uuid &uid, int type) const {
        std::shared_lock<std::shared_mutex> lock(_mutex);
        UuidTypeKey utk;
        utk.uid = uid;
        utk.type = type;
        auto it = _offsetIndex.find(utk);
        if (it != _offsetIndex.end())
            return it->second;
        return 0;
    }

    void RecordIndex::listByType(int type, std::vector<uuid::uuid> &out) const {
        std::shared_lock<std::shared_mutex> lock(_mutex);
        for (auto &[key, offset] : _offsetIndex) {
            if (key.type == type)
                out.emplace_back(key.uid);
        }
    }

    void RecordIndex::invalidate() {
        std::unique_lock<std::shared_mutex> lock(_mutex);
        _valid = false;
    }

    bool RecordIndex::valid() const {
        std::shared_lock<std::shared_mutex> lock(_mutex);
        return _valid;
    }

    size_t RecordIndex::revision() const {
        std::shared_lock<std::shared_mutex> lock(_mutex);
        return _revision;
    }

    RecordIndex &getIndex(const std::string &path) {
        std::lock_guard<std::mutex> guard(g_indexMapMutex);
        auto &ptr = g_indexMap[path];
        if (!ptr)
            ptr = std::make_unique<RecordIndex>();
        return *ptr;
    }

    RecordIndex &AuthBackend::getIndex() {
        return authdb::getIndex(_Options);
    }
}
