#pragma once

#include <saas/library/rtyt/lib/cypress/cypress.h>
#include <saas/library/rtyt/lib/operation/context.h>
#include <mapreduce/yt/interface/client.h>
#include <mapreduce/yt/interface/operation.h>

#include <google/protobuf/dynamic_message.h>

namespace NRTYT {
    class TClientBase: virtual public NYT::IClientBase {
    protected:
        using TYPath = NYT::TYPath;
        using TRichYPath = NYT::TRichYPath;
        using ENodeType = NYT::ENodeType;
        using TNodeId = NYT::TNodeId;
        using TNode = NYT::TNode;
        using TOperationId = NYT::TOperationId;
        using TJobId = NYT::TJobId;
        using IOperationPtr = NYT::IOperationPtr;
    public:
        // Pass Nothing() to create an in-memory cypress, where table creating is prohibited 
        // Mounting other cypresses is still allowed
        TClientBase(TMaybe<TFsPath> storageRoot = Nothing(), bool failIfNotExist = false);

        NYT::ITransactionPtr StartTransaction(
            const NYT::TStartTransactionOptions& options) override;

        // cypress

        TNodeId Create(
            const TYPath& path,
            ENodeType type,
            const NYT::TCreateOptions& options = NYT::TCreateOptions()) override;

        void Remove(
            const TYPath& path,
            const NYT::TRemoveOptions& options) override;

        bool Exists(
            const TYPath& path,
            const NYT::TExistsOptions& options = NYT::TExistsOptions()) override;

        TNode Get(
            const TYPath& path,
            const NYT::TGetOptions& options = NYT::TGetOptions()) override;

        void Set(
            const TYPath& path,
            const TNode& value,
            const NYT::TSetOptions& options) override;

        void MultisetAttributes(
            const TYPath& path,
            const TNode::TMapType& value,
            const NYT::TMultisetAttributesOptions& options) override;

        TNode::TListType List(
            const TYPath& path,
            const NYT::TListOptions& options) override;

        TNodeId Copy(
            const TYPath& sourcePath,
            const TYPath& destinationPath,
            const NYT::TCopyOptions& options) override;

        TNodeId Move(
            const TYPath& sourcePath,
            const TYPath& destinationPath,
            const NYT::TMoveOptions& options) override;

        TNodeId Link(
            const TYPath& targetPath,
            const TYPath& linkPath,
            const NYT::TLinkOptions& options) override;

        void Concatenate(
            const TVector<TRichYPath>& sourcePaths,
            const TRichYPath& destinationPath,
            const NYT::TConcatenateOptions& options) override;

        TRichYPath CanonizeYPath(const TRichYPath& path) override;

        TVector<NYT::TTableColumnarStatistics> GetTableColumnarStatistics(
            const TVector<TRichYPath>& paths,
            const NYT::TGetTableColumnarStatisticsOptions& options) override;

        TMaybe<TYPath> GetFileFromCache(
            const TString& md5Signature,
            const TYPath& cachePath,
            const NYT::TGetFileFromCacheOptions& options = NYT::TGetFileFromCacheOptions()) override;

        TYPath PutFileToCache(
            const TYPath& filePath,
            const TString& md5Signature,
            const TYPath& cachePath,
            const NYT::TPutFileToCacheOptions& options = NYT::TPutFileToCacheOptions()) override;

        NYT::IFileReaderPtr CreateFileReader(
            const TRichYPath& path,
            const NYT::TFileReaderOptions& options) override;

        NYT::IFileWriterPtr CreateFileWriter(
            const TRichYPath& path,
            const NYT::TFileWriterOptions& options) override;

        NYT::TTableWriterPtr<NYT::TNode> CreateTableWriter(
            const TRichYPath& path,
            const NYT::TTableWriterOptions& options = NYT::TTableWriterOptions());
        NYT::TTableWriterPtr< ::google::protobuf::Message> CreateTableWriter(
            const TRichYPath& path,
            const ::google::protobuf::Descriptor& descriptor,
            const NYT::TTableWriterOptions& options = NYT::TTableWriterOptions()) override;

        NYT::TRawTableReaderPtr CreateRawReader(
            const TRichYPath& path,
            const NYT::TFormat& format,
            const NYT::TTableReaderOptions& options) override;

        NYT::TRawTableWriterPtr CreateRawWriter(
            const TRichYPath& path,
            const NYT::TFormat& format,
            const NYT::TTableWriterOptions& options) override;

        NYT::IFileReaderPtr CreateBlobTableReader(
            const TYPath& path,
            const NYT::TKey& key,
            const NYT::TBlobTableReaderOptions& options) override;

        // operations

        IOperationPtr DoMap(
            const NYT::TMapOperationSpec& spec,
            ::TIntrusivePtr<NYT::IStructuredJob> mapper,
            const NYT::TOperationOptions& options) override;

        IOperationPtr RawMap(
            const NYT::TRawMapOperationSpec& spec,
            ::TIntrusivePtr<NYT::IRawJob> mapper,
            const NYT::TOperationOptions& options) override;

        IOperationPtr DoReduce(
            const NYT::TReduceOperationSpec& spec,
            ::TIntrusivePtr<NYT::IStructuredJob> reducer,
            const NYT::TOperationOptions& options) override;

        IOperationPtr RawReduce(
            const NYT::TRawReduceOperationSpec& spec,
            ::TIntrusivePtr<NYT::IRawJob> mapper,
            const NYT::TOperationOptions& options) override;

        IOperationPtr DoJoinReduce(
            const NYT::TJoinReduceOperationSpec& spec,
            ::TIntrusivePtr<NYT::IStructuredJob> reducer,
            const NYT::TOperationOptions& options) override;

        IOperationPtr RawJoinReduce(
            const NYT::TRawJoinReduceOperationSpec& spec,
            ::TIntrusivePtr<NYT::IRawJob> mapper,
            const NYT::TOperationOptions& options) override;

        IOperationPtr DoMapReduce(
            const NYT::TMapReduceOperationSpec& spec,
            ::TIntrusivePtr<NYT::IStructuredJob> mapper,
            ::TIntrusivePtr<NYT::IStructuredJob> reduceCombiner,
            ::TIntrusivePtr<NYT::IStructuredJob> reducer,
            const NYT::TOperationOptions& options) override;

        IOperationPtr RawMapReduce(
            const NYT::TRawMapReduceOperationSpec& spec,
            ::TIntrusivePtr<NYT::IRawJob> mapper,
            ::TIntrusivePtr<NYT::IRawJob> reduceCombiner,
            ::TIntrusivePtr<NYT::IRawJob> reducer,
            const NYT::TOperationOptions& options) override;

        IOperationPtr Sort(
            const NYT::TSortOperationSpec& spec,
            const NYT::TOperationOptions& options = NYT::TOperationOptions()) override;

        IOperationPtr Merge(
            const NYT::TMergeOperationSpec& spec,
            const NYT::TOperationOptions& options) override;

        IOperationPtr Erase(
            const NYT::TEraseOperationSpec& spec,
            const NYT::TOperationOptions& options) override;

        IOperationPtr RemoteCopy(
            const NYT::TRemoteCopyOperationSpec& spec,
            const NYT::TOperationOptions& options = NYT::TOperationOptions()) override;

        IOperationPtr RunVanilla(
            const NYT::TVanillaOperationSpec& spec,
            const NYT::TOperationOptions& options = NYT::TOperationOptions()) override;

        IOperationPtr AttachOperation(const NYT::TOperationId& operationId) override;

        NYT::EOperationBriefState CheckOperation(const NYT::TOperationId& operationId) override;

        void AbortOperation(const NYT::TOperationId& operationId) override;

        void CompleteOperation(const NYT::TOperationId& operationId) override;

        void WaitForOperation(const NYT::TOperationId& operationId) override;

        void AlterTable(
            const TYPath& path,
            const NYT::TAlterTableOptions& options) override;

        NYT::TBatchRequestPtr CreateBatchRequest() override;

        NYT::IClientPtr GetParentClient() override;

        NYT::TNodeId GetNodeId(const NYT::TYPath& path);
        void Sync(TMaybe<NYT::TNodeId> mountId = Nothing(), bool needFlush = true); // empty for saving root cypress

        // if there is no cypress by this path, there will be a new empty cypress          
        void Mount(const TFsPath& storagePath, const NYT::TYPath& mountPoint, bool failIfNotExist = false);
        void Umount(NYT::TNodeId mountId);
    private:
        ::TIntrusivePtr<TCypressClient> CypressClient;

    private:
        bool CheckReducerInputTables(const NYT::TReduceOperationSpec& spec) const {
            Y_UNUSED(spec);
            return true;
        }
        void CreateOperationOutput(const NYT::TRichYPath& path, const ::google::protobuf::Descriptor* descriptor);
        void CreateOperationOutput(const NYT::TOperationOutputSpecBase& spec);
        void FillOperationInputs(const NYT::TOperationInputSpecBase& spec, TInputContext& context);
        void FillOperationOutputs(const NYT::TOperationOutputSpecBase& spec, TOutputContext& context);

        const ::google::protobuf::Descriptor* RestoreProtoFromAttrs(const NYT::TRichYPath& path);

        TInputContext GetMapInputContext(const NYT::TMapOperationSpec& spec);
        TOutputContext GetMapOutputContext(const NYT::TMapOperationSpec& spec);

        TInputContext GetReduceInputContext(const NYT::TReduceOperationSpec& spec);
        TOutputContext GetReduceOutputContext(const NYT::TReduceOperationSpec& spec);

        TInputContext GetSortInputContext(const NYT::TSortOperationSpec& spec);
        TOutputContext GetSortOutputContext(const NYT::TSortOperationSpec& spec);
    private:
        ::TIntrusivePtr<NYT::INodeReaderImpl> CreateNodeReader(
            const NYT::TRichYPath& path, const NYT::TTableReaderOptions& options) override;

        ::TIntrusivePtr<NYT::IYaMRReaderImpl> CreateYaMRReader(
            const NYT::TRichYPath& path, const NYT::TTableReaderOptions& options) override;

        ::TIntrusivePtr<NYT::IProtoReaderImpl> CreateProtoReader(
            const NYT::TRichYPath& path,
            const NYT::TTableReaderOptions& options,
            const ::google::protobuf::Message* prototype) override;

        ::TIntrusivePtr<NYT::ISkiffRowReaderImpl> CreateSkiffRowReader(
            const NYT::TRichYPath& path,
            const NYT::TTableReaderOptions& options,
            const NYT::ISkiffRowSkipperPtr& skipper,
            const NSkiff::TSkiffSchemaPtr& schema) override;

        ::TIntrusivePtr<NYT::INodeWriterImpl> CreateNodeWriter(
            const NYT::TRichYPath& path, const NYT::TTableWriterOptions& options) override;

        ::TIntrusivePtr<NYT::IYaMRWriterImpl> CreateYaMRWriter(
            const NYT::TRichYPath& path, const NYT::TTableWriterOptions& options) override;

        ::TIntrusivePtr<NYT::IProtoWriterImpl> CreateProtoWriter(
            const NYT::TRichYPath& path,
            const NYT::TTableWriterOptions& options,
            const ::google::protobuf::Message* prototype) override;
    };

    class TTransaction : public NYT::ITransaction, public TClientBase {
        // dummy implementation, does nothing
        using TYPath = NYT::TYPath;
        using TRichYPath = NYT::TRichYPath;
        using ENodeType = NYT::ENodeType;
        using TNodeId = NYT::TNodeId;
        using TNode = NYT::TNode;
        using TOperationId = NYT::TOperationId;
        using TJobId = NYT::TJobId;
        using IOperationPtr = NYT::IOperationPtr;

        NYT::TTransactionId Id;
    public:
        TTransaction(const TClientBase& client) : TClientBase(client) {
        }

        // ITransaction
        const NYT::TTransactionId& GetId() const override;
        NYT::ILockPtr Lock(
            const NYT::TYPath& path,
            NYT::ELockMode mode,
            const NYT::TLockOptions& options = NYT::TLockOptions()) override;
        void Unlock(
            const NYT::TYPath& path,
            const NYT::TUnlockOptions& options = NYT::TUnlockOptions()) override;
        void Commit() override;
        void Abort() override;
        void Ping() override;
        void Detach() override;
    };

    class TClient : public NYT::IClient, public TClientBase {
        // dummy implementation, does nothing

        using TYPath = NYT::TYPath;
        using TRichYPath = NYT::TRichYPath;
        using ENodeType = NYT::ENodeType;
        using TNodeId = NYT::TNodeId;
        using TNode = NYT::TNode;
        using TOperationId = NYT::TOperationId;
        using TJobId = NYT::TJobId;
        using IOperationPtr = NYT::IOperationPtr;

    public:
        TClient(const TClientBase& client) : TClientBase(client) {
        }

        // IClient
        [[nodiscard]] NYT::ITransactionPtr AttachTransaction(
            const NYT::TTransactionId& transactionId,
            const NYT::TAttachTransactionOptions& options = NYT::TAttachTransactionOptions()) override;
        void MountTable(
            const TYPath& path,
            const NYT::TMountTableOptions& options = NYT::TMountTableOptions()) override;
        void UnmountTable(
            const TYPath& path,
            const NYT::TUnmountTableOptions& options = NYT::TUnmountTableOptions()) override;
        void RemountTable(
            const TYPath& path,
            const NYT::TRemountTableOptions& options = NYT::TRemountTableOptions()) override;

        void FreezeTable(
            const TYPath& path,
            const NYT::TFreezeTableOptions& options = NYT::TFreezeTableOptions()) override;
        void UnfreezeTable(
            const TYPath& path,
            const NYT::TUnfreezeTableOptions& options = NYT::TUnfreezeTableOptions()) override;
        void ReshardTable(
            const TYPath& path,
            const TVector<NYT::TKey>& pivotKeys,
            const NYT::TReshardTableOptions& options = NYT::TReshardTableOptions()) override;
        void ReshardTable(
            const TYPath& path,
            i64 tabletCount,
            const NYT::TReshardTableOptions& options = NYT::TReshardTableOptions()) override;

        void InsertRows(
            const TYPath& path,
            const TNode::TListType& rows,
            const NYT::TInsertRowsOptions& options = NYT::TInsertRowsOptions()) override;
        void DeleteRows(
            const TYPath& path,
            const TNode::TListType& keys,
            const NYT::TDeleteRowsOptions& options = NYT::TDeleteRowsOptions()) override;
        void TrimRows(
            const TYPath& path,
            i64 tabletIndex,
            i64 rowCount,
            const NYT::TTrimRowsOptions& options = NYT::TTrimRowsOptions()) override;
        TNode::TListType LookupRows(
            const TYPath& path,
            const TNode::TListType& keys,
            const NYT::TLookupRowsOptions& options = NYT::TLookupRowsOptions()) override;
        TNode::TListType SelectRows(
            const TString& query,
            const NYT::TSelectRowsOptions& options = NYT::TSelectRowsOptions()) override;

        void AlterTableReplica(
            const NYT::TReplicaId& replicaId,
            const NYT::TAlterTableReplicaOptions& alterTableReplicaOptions) override;

        ui64 GenerateTimestamp() override;
        NYT::TAuthorizationInfo WhoAmI() override;

        NYT::TOperationAttributes GetOperation(
            const TOperationId& operationId,
            const NYT::TGetOperationOptions& options = NYT::TGetOperationOptions()) override;
        NYT::TListOperationsResult ListOperations(
            const NYT::TListOperationsOptions& options = NYT::TListOperationsOptions()) override;
        void UpdateOperationParameters(
            const TOperationId& operationId,
            const NYT::TUpdateOperationParametersOptions& options) override;
        NYT::TJobAttributes GetJob(
            const TOperationId& operationId,
            const TJobId& jobId,
            const NYT::TGetJobOptions& options = NYT::TGetJobOptions()) override;
        NYT::TListJobsResult ListJobs(
            const TOperationId& operationId,
            const NYT::TListJobsOptions& options = NYT::TListJobsOptions()) override;
        NYT::IFileReaderPtr GetJobInput(
            const TJobId& jobId,
            const NYT::TGetJobInputOptions& options = NYT::TGetJobInputOptions()) override;
        NYT::IFileReaderPtr GetJobFailContext(
            const TOperationId& operationId,
            const TJobId& jobId,
            const NYT::TGetJobFailContextOptions& options = NYT::TGetJobFailContextOptions()) override;
        NYT::IFileReaderPtr GetJobStderr(
            const TOperationId& operationId,
            const TJobId& jobId,
            const NYT::TGetJobStderrOptions& options = NYT::TGetJobStderrOptions()) override;
        TNode::TListType SkyShareTable(
            const std::vector<TYPath>& tablePaths,
            const NYT::TSkyShareTableOptions& options) override;
        NYT::TCheckPermissionResponse CheckPermission(
            const TString& user,
            NYT::EPermission permission,
            const TYPath& path,
            const NYT::TCheckPermissionOptions& options = NYT::TCheckPermissionOptions()) override;
        TVector<NYT::TTabletInfo> GetTabletInfos(
            const TYPath& path,
            const TVector<int>& tabletIndexes,
            const NYT::TGetTabletInfosOptions& options = NYT::TGetTabletInfosOptions()) override;
        void SuspendOperation(
            const TOperationId& operationId,
            const NYT::TSuspendOperationOptions& options = NYT::TSuspendOperationOptions()) override;
        void ResumeOperation(
            const TOperationId& operationId,
            const NYT::TResumeOperationOptions& options = NYT::TResumeOperationOptions()) override;
    };

    ::TIntrusivePtr<TClient> MakeClient(TMaybe<TFsPath> storageRoot = Nothing(), bool failIfNotExist = false);
}
