/*******************************************************************************
 * 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 <iostream>
#include <cstring>
#include <vector>
#include <memory>

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

authdb::GroupData::GroupData(uuid::uuid gid) : AuthData(gid){
    _Name=nullptr;
    _Members=nullptr;
    Data.type=DataType::GroupData;
}

authdb::GroupData::GroupData(const GroupData& src) : AuthData(src){
    _Name=nullptr;
    _Members=nullptr;
    Data.type=DataType::GroupData;
}


authdb::GroupData::~GroupData(){
}

void authdb::GroupData::setName(const std::string &name){
    setAuthData<char>(DataType::GroupData,StorageType::TextStorage, this,&_Name,"groupname",name.c_str(),name.length()+1);
}

const std::string authdb::GroupData::getName(){
     try{
        const char *res=getAuthData<char>(DataType::GroupData,StorageType::TextStorage, this,&_Name,"groupname");
        if(res)
            return res;
        else
            return "";
    }catch(AuthBackendError &e){
        std::cerr << e.what() << std::endl;
        return "";
    }
}

void authdb::GroupData::addMember(const uuid::uuid &uid){
    try{
       if(getAuthData<char>(DataType::GroupData,StorageType::BinaryStorage,
                                                                        this,&_Members,"groupmembers")){
       }
    }catch(AuthBackendError &){
    }

    std::vector<uuid::uuid> members;

    if(_Members && _Members->datasize!=0){

        for(size_t i =0; i<_Members->datasize; i+=sizeof(uuid::uuid_t) ){
            uuid::uuid_t tmp;
            std::copy(_Members->data+i,_Members->data+(i+sizeof(uuid::uuid_t)),&tmp[0]);
            if(uid==tmp){
                throw AuthBackendError("User is already member!");
            }
            members.emplace_back(tmp);
        }
    }

    members.emplace_back(uid);

    char *data =new  char[members.size()*sizeof(uuid::uuid_t)];

    size_t wsize=0;

    for(const uuid::uuid &mem : members){
        std::copy(
           &mem.value[0],
           &mem.value[0]+sizeof(uuid::uuid_t),
           data+wsize
        );
        wsize+=sizeof(uuid::uuid_t);
    }

    setAuthData<char>(DataType::GroupData,StorageType::BinaryStorage,this,&_Members,"groupmembers",data,wsize);

    delete[] data;
}

void authdb::GroupData::remMember(const uuid::uuid& uid){

    if(!getAuthData<char>(DataType::GroupData,StorageType::BinaryStorage,
         this,&_Members,"groupmembers"))
        return;

    std::vector<uuid::uuid> members;

    for(size_t i =0; i<_Members->datasize; i+=sizeof(uuid::uuid_t) ){
        uuid::uuid_t tmp;
        std::copy(_Members->data+i,_Members->data+i+sizeof(uuid::uuid_t),&tmp[0]);
        if(uid!=tmp)
            members.push_back(tmp);
    }

    char *data =new  char[members.size()*sizeof(uuid::uuid_t)];

    size_t wsize=0;

    for(const uuid::uuid &mem : members){
        std::copy(
           &mem.value[0],
           &mem.value[0]+sizeof(uuid::uuid_t),
           data+wsize
        );
        wsize+=sizeof(uuid::uuid_t);
    }

    setAuthData<char>(DataType::GroupData,StorageType::BinaryStorage,this,&_Members,"groupmembers",data,wsize);

    delete[] data;
}


bool authdb::GroupData::isMember(const uuid::uuid &uid){
    if(!getAuthData<char>(DataType::GroupData,StorageType::BinaryStorage,
                                                                        this,&_Members,"groupmembers"))
        return false;

    for(size_t i =0; i<_Members->datasize; i+=sizeof(uuid::uuid_t) ){
        uuid::uuid_t tmp;
        std::copy(_Members->data+i,_Members->data+i+sizeof(uuid::uuid_t),&tmp[0]);
        if(uid==tmp)
            return true;
    }

    return false;
}

void authdb::GroupData::getMembers(std::vector<uuid::uuid>& members){
    if(!getAuthData<char>(DataType::GroupData,StorageType::BinaryStorage,
                                                                        this,&_Members,"groupmembers"))
        return;

    char *start=_Members->data;

    for(size_t i =0; i<_Members->datasize; i+=sizeof(uuid::uuid_t) ){
        uuid::uuid_t tmp;
        std::copy(start+i,start+i+sizeof(uuid::uuid_t),&tmp[0]);
        members.emplace_back(tmp);
    }
}


bool authdb::Group::exits(AuthBackend& backend,const uuid::uuid &gid, const std::string &name, int& ret){
    AuthBackend::Guard guard(backend, AuthBackend::Shared);
    RecordIndex &idx = backend.getIndex();
    if (!idx.valid() || idx.revision() != backend.getRevesion()) {
        idx.build(backend);
    }

    if (!name.empty()) {
        uuid::uuid found;
        if (idx.findByName(DataType::GroupData, "groupname", name, found)) {
            ret = 1;
            return true;
        }
    }

    if (!gid.empty() && idx.findByUuid(DataType::GroupData, gid)) {
        ret = 2;
        return true;
    }

    ret = -1;
    return false;
}

void authdb::Group::create(AuthBackend& backend, class GroupData* dat){
    AuthBackend::Guard guard(backend);
    int exi=0;

    if(uuid::uuid(dat->Data.ruid).empty())
        throw authdb::AuthBackendError("Create Group: GUID not set !");

    if(exits(backend,dat->Data.ruid,dat->getName(),exi)){
       switch(exi){
           case 1:
                throw authdb::AuthBackendError("Create Group: Groupname already Exists aborting!");
           default:
                throw authdb::AuthBackendError("Create Group: GUID already Exists aborting!");
       }
    }

     backend.createRecord(*dat,DataType::GroupData);
}

void authdb::Group::info(AuthBackend &backend, class GroupData& dat, size_t &pos){
    if(uuid::uuid(dat.Data.ruid).empty())
        throw AuthBackendError("group info gid required!");

    if(!backend.getRecord(dat,DataType::GroupData))
        return;
}

void authdb::Group::remove(AuthBackend& backend, const uuid::uuid &gid){
    backend.delRecord(gid,DataType::GroupData);
}

void authdb::Group::edit(AuthBackend &backend, class GroupData &dat, size_t &pos){
    backend.editRecord(dat, DataType::GroupData);
}

void authdb::Group::list(AuthBackend &backend,std::vector<uuid::uuid> &uuids){
    AuthBackend::Guard guard(backend, AuthBackend::Shared);
    RecordIndex &idx = backend.getIndex();
    if(!idx.valid() || idx.revision()!=backend.getRevesion())
        idx.build(backend);
    idx.listByType(DataType::GroupData, uuids);
}
