/*******************************************************************************
 * 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 <iostream>

#include "client.h"
#include "exception.h"
#include "backend.h"
#include "index.h"

namespace authdb{
    class ConnectionData {
    private:
            ConnectionData(uuid::uuid &id){
                _Id=id;
                _next=nullptr;
            };

            uuid::uuid      _Id;
            ConnectionData *_next;
            friend class Client;
    };

    ConnectionData *_firstConnection=nullptr;
    ConnectionData *_lastConnection=nullptr;
    std::mutex           _lockConnection;
};

authdb::ClientData::ClientData(uuid::uuid clid) : AuthData(clid.value){
    ClientKey=nullptr;
    ClientName=nullptr;
    ClientKrbKey=nullptr;
}

authdb::ClientData::~ClientData(){
}

void authdb::ClientData::setKey(const char *key){
    setAuthData<char>(DataType::ClientData,StorageType::TextStorage,this,&ClientKey,"clientkey",key,strlen(key)+1);
}

const char *authdb::ClientData::getKey(){
    return getAuthData<char>(DataType::ClientData,StorageType::TextStorage,
                             this,&ClientKey,"clientkey");
}

void authdb::ClientData::setName(const char* name){
    setAuthData<char>(DataType::ClientData,StorageType::TextStorage, this,&ClientName,"clientname",name,strlen(name)+1);
}


const char *authdb::ClientData::getName(){
    return getAuthData<char>(DataType::ClientData,StorageType::TextStorage,
                             this,&ClientName,"clientname");
}

void authdb::ClientData::setKrbKey(const std::string &src){
    setAuthData<char>(DataType::ClientData,StorageType::TextStorage,this,&ClientKrbKey,"krbkey",src.c_str(),src.length()+1);
}

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

void authdb::Client::create(AuthBackend& backend, class ClientData* data){
    AuthBackend::Guard guard(backend);
    int exi=0;

    if(exits(backend,data->Data.ruid,data->getName(),exi)){
        switch(exi){
            case 1:
                throw authdb::AuthBackendError("Create Client: Clientname already Exists aborting!");
            default:
                throw authdb::AuthBackendError("Create Client: UUID already Exists aborting!");
        }
    }

    backend.createRecord(*data,DataType::ClientData);
}

bool authdb::Client::exits(AuthBackend& backend,const uuid::uuid &clid, const char* 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 && name[0] != '\0') {
        uuid::uuid found;
        if (idx.findByName(DataType::ClientData, "clientname", name, found)) {
            ret = 1;
            return true;
        }
    }

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

    ret = -1;
    return false;
}

void authdb::Client::info(AuthBackend& backend, class ClientData& dat, size_t& pos){
    if(uuid::uuid(dat.Data.ruid).empty())
        throw AuthBackendError("client info uid required!");

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

    dat.ClientName=&dat.Data;
}

authdb::ClientConnections::ClientConnections(uuid::uuid sid, AuthBackend& bck, const class ClientData& clt) : _backend(bck) , _Data(clt){
    _sessionId=sid;
    _next=nullptr;
    _accessed=std::chrono::steady_clock::now();
}

authdb::ClientConnections *authdb::ClientConnections::next(){
    return _next;
}

void authdb::Client::remove(AuthBackend& backend, const uuid::uuid &clid){
    backend.delRecord(clid.value,DataType::ClientData);
}

void authdb::Client::edit(AuthBackend &backend, class ClientData &dat){
    backend.editRecord(dat, DataType::ClientData);
}

authdb::AuthBackend * authdb::ClientConnections::getBackend(){
    return &_backend;
}

const uuid::uuid authdb::ClientConnections::getId(){
    return _sessionId;
}



bool authdb::ClientConnections::operator==(uuid::uuid sessid){
    if(sessid == _sessionId)
        return true;
    return false;
}

authdb::Client::Client(){
    _firstConnection=nullptr;
    _lastConnection=nullptr;
}

authdb::Client::Client(const class Client &src){
    for(ClientConnections *curcon=src._firstConnection; curcon; curcon=curcon->next()){
        if(_firstConnection){
            _lastConnection->_next=new ClientConnections(curcon->_sessionId,curcon->_backend,curcon->_Data);
            _lastConnection=_lastConnection->_next;
        }else{
            _firstConnection=new  ClientConnections(curcon->_sessionId,curcon->_backend,curcon->_Data);
            _lastConnection=_firstConnection;
        }
    }
}

authdb::Client::~Client(){
    ClientConnections *cur=_firstConnection;
    while(cur){
        ClientConnections *next=cur->_next;
        cur->_next=nullptr;
        delete cur;
        cur=next;
    }
    _firstConnection=nullptr;
    _lastConnection=nullptr;
}

authdb::ClientConnections *authdb::Client::addConnection(AuthBackend &backend,const class ClientData &data){
    std::lock_guard<std::mutex> guard(_lockConnection);

    cleanupExpiredConnections();

    uuid::uuid ncid;

    ncid.generate();

    ClientConnections *conn;
    if(_firstConnection){
        _lastConnection->_next=new ClientConnections(ncid,backend,data);
        _lastConnection=_lastConnection->_next;
        conn = _lastConnection;
    }else{
        _firstConnection=new ClientConnections(ncid,backend,data);
        _lastConnection=_firstConnection;
        conn = _firstConnection;
    }
    _connectionIndex[ncid] = conn;
    return conn;
}

void authdb::Client::delConnection(const uuid::uuid &conid){
    std::lock_guard<std::mutex> guard(_lockConnection);
    _connectionIndex.erase(conid);
    ClientConnections *prev=nullptr;
    for(ClientConnections *curdat=_firstConnection; curdat; ){
        if(curdat->_sessionId==conid){
            ClientConnections *next=curdat->_next;
            if(prev){
                prev->_next=next;
            }else{
                _firstConnection=next;
            }
            if(curdat==_lastConnection){
                _lastConnection=prev;
            }
            curdat->_next=nullptr;
            delete curdat;
            curdat=next;
            break;
        }else{
            prev=curdat;
            curdat=curdat->_next;
        }
    }
}

void authdb::Client::cleanupExpiredConnections(){
    auto now=std::chrono::steady_clock::now();
    ClientConnections *prev=nullptr;
    ClientConnections *curdat=_firstConnection;
    while(curdat){
        auto age=std::chrono::duration_cast<std::chrono::hours>(now - curdat->_accessed).count();
        if(age >= 24){
            ClientConnections *next=curdat->_next;
            _connectionIndex.erase(curdat->_sessionId);
            if(prev){
                prev->_next=next;
            }else{
                _firstConnection=next;
            }
            if(curdat==_lastConnection){
                _lastConnection=prev;
            }
            curdat->_next=nullptr;
            delete curdat;
            curdat=next;
        }else{
            prev=curdat;
            curdat=curdat->_next;
        }
    }
}

bool authdb::Client::checkConnection(const uuid::uuid &conid) const{
    std::lock_guard<std::mutex> guard(_lockConnection);
    return _connectionIndex.count(conid) > 0;
}

authdb::ClientConnections *authdb::Client::findConnection(const uuid::uuid &conid){
    std::lock_guard<std::mutex> guard(_lockConnection);
    auto it = _connectionIndex.find(conid);
    if (it != _connectionIndex.end()){
        it->second->_accessed = std::chrono::steady_clock::now();
        return it->second;
    }
    return nullptr;
}

authdb::ClientConnections * authdb::Client::ConnectionData(){
    return _firstConnection;
}

void authdb::Client::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::ClientData, uuids);
}
