/*******************************************************************************
 * 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 <algorithm>
#include <iterator>
#include <cstring>
#include <mutex>
#include <uuidp.h>

#include "backend.h"
#include "types.h"
#include "exception.h"

#pragma once

#define GETDATA(file) ( DATAPATH "/" #file )

#define VERSION 1

namespace authdb {
    class Client;
    class Session;

    extern class Client   AuthClient;
    extern std::mutex     AuthClientLock;
    extern Session        *AuthSession;

    struct AuthHeader{
        int    start = 0xFE;
        char   domain[255]={0};
        size_t Version=0;
        size_t Revesion=0;
    };

    class AuthData {
    public:
        AuthData(const uuid::uuid &id);

        AuthData(const AuthData &src);

        ~AuthData();

        AuthData *next() const;

        struct Record {
            int           start = 0xFE;
            unsigned char ruid[16] = {0};
            int           type = EmptyData;
            int           storage = EmptyStorage;
            char          fieldname[255] = {0};
            size_t        datasize = 0;
            char         *data = nullptr;
            size_t        count = 0;
        } Data;

        bool empty();

        AuthData& append(const AuthData &src);
        AuthData& append(const AuthData::Record &src);

    protected:
        AuthData *_next;
    private:
        friend    int  searchValue(authdb::AuthBackend &backend,const char*fieldname,const char *value);
        friend    void createRecord(AuthBackend &backend,AuthData &rec,int type);
        friend    bool getRecord(authdb::AuthBackend &backend,AuthData &rec,int type);
        friend    bool editRecord(AuthBackend &backend,AuthData &rec,int type);
        friend    void delRecord(authdb::AuthBackend &backend,const uuid::uuid &uid,int type);
    };

    template<typename T>
    void setAuthData(int type,int storage,AuthData *dat,AuthData::Record **varclass, const char *varname,const T *source,size_t sourcelen){
        AuthData::Record *target=*varclass;

        AuthData *curec=dat;

        uuid::uuid gid(dat->Data.ruid);

        if(!target){
            while(curec){
                if(curec->Data.type == type &&
                    gid==uuid::uuid(curec->Data.ruid) &&
                    strcmp(curec->Data.fieldname,varname)==0
                ){
                    target=&curec->Data;
                    target->datasize=0;
                    delete[] target->data;
                    target->data=nullptr;
                    break;
                }
                curec=curec->next();
            }
        }

        if(!target){
            AuthData::Record tmp;
            target=&dat->append(tmp).Data;
        }

        target->start=0xFE;
        std::copy(&gid.value[0], &gid.value[0]+sizeof(unsigned char[16]), target->ruid);
        target->type=type;
        target->storage=storage;
        memset(target->fieldname,0,255);
        strcpy(target->fieldname,varname);
        target->datasize=sourcelen;
        target->data=reinterpret_cast<char*>(new T[target->datasize]);
        std::copy(reinterpret_cast<const T*>(source),reinterpret_cast<const T*>(source)+target->datasize,reinterpret_cast<T*>(target->data));
        *varclass=target;
    }

    template<class T>
    const T *getAuthData(int type,int storage,class AuthData *dat,AuthData::Record **varclass,const char *varname){
        if(varclass ==nullptr ){
            throw AuthBackendError("getAuthData invalid pointer !");
        }else{
            for(AuthData *curdat=dat; curdat; curdat=curdat->next()){
                if(curdat->Data.type==type && curdat->Data.storage==storage
                    && strcmp(curdat->Data.fieldname,varname)==0 ){
                    (*varclass)=&curdat->Data;
                    return reinterpret_cast<T*>((*varclass)->data);
                }
            }
        }

        *varclass=nullptr;

        std::string err;
        err=varname;
        err+=" data not found !";
        throw(authdb::AuthBackendError(err.c_str()));
    }

    void createRecord(authdb::AuthBackend &backend,AuthData &rec,int type);
    bool getRecord(authdb::AuthBackend &backend,AuthData &rec,int type);
    bool editRecord(AuthBackend &backend,AuthData &rec,int type);
    void delRecord(authdb::AuthBackend &backend,const uuid::uuid &uid,int type);

    int searchValue(authdb::AuthBackend &backend,const char*fieldname,const char *value);

    void vacuumRecords(AuthBackend &backend);

    struct DomainInfo {
        int         storageType;
        std::string storageOptions;
        std::string domainName;
        bool        found = false;
    };

    class DomainBackend {
    public:
        DomainBackend(authdb::AuthBackend &adminbck);

        // Lookup domain metadata only (fast, call under admin lock)
        DomainInfo resolve(uuid::uuid did);

        // Create backend from resolved info (slow, call WITHOUT admin lock)
        authdb::AuthBackend *create(const DomainInfo &info);

        // Combined: resolve + create (legacy, caller must handle locking)
        authdb::AuthBackend *data(uuid::uuid did);

        ~DomainBackend();

    private:
        authdb::AuthBackend &_adminbck;
        authdb::AuthBackend *_domainbck;
    };
};
