#include "env.h"

#include <build/scripts/c_templates/svnversion.h>

#include <util/folder/dirut.h>
#include <util/folder/path.h>
#include <util/generic/singleton.h>
#include <util/stream/file.h>
#include <util/stream/fwd.h>
#include <util/system/env.h>
#include <util/system/file.h>
#include <util/system/file_lock.h>
#include <util/system/guard.h>

#include <library/cpp/json/json_reader.h>
#include <library/cpp/json/json_value.h>
#include <library/cpp/json/json_writer.h>

TString ArcadiaSourceRoot() {
    if (const auto& sourceRoot = NPrivate::GetTestEnv().SourceRoot) {
        return sourceRoot;
    } else {
        return GetArcadiaSourcePath();
    }
}

TString BuildRoot() {
    if (const auto& buildRoot = NPrivate::GetTestEnv().BuildRoot) {
        return buildRoot;
    } else {
        return GetArcadiaSourcePath();
    }
}

TString ArcadiaFromCurrentLocation(TStringBuf where, TStringBuf path) {
    return (TFsPath(ArcadiaSourceRoot()) / TFsPath(where).Parent() / path).Fix();
}

TString BinaryPath(TStringBuf path) {
    return (TFsPath(BuildRoot()) / path).Fix();
}

TString GetArcadiaTestsData() {
    TString atdRoot = NPrivate::GetTestEnv().ArcadiaTestsDataDir;
    if (atdRoot) {
        return atdRoot;
    }

    TString path = NPrivate::GetCwd();
    const char pathsep = GetDirectorySeparator();
    while (!path.empty()) {
        TString dataDir = path + "/arcadia_tests_data";
        if (IsDir(dataDir)) {
            return dataDir;
        }

        size_t pos = path.find_last_of(pathsep);
        if (pos == TString::npos) {
            pos = 0;
        }
        path.erase(pos);
    }

    return {};
}

TString GetWorkPath() {
    TString workPath = NPrivate::GetTestEnv().WorkPath;
    if (workPath) {
        return workPath;
    }

    return NPrivate::GetCwd();
}

TFsPath GetOutputPath() {
    return GetWorkPath() + "/testing_out_stuff";
}

const TString& GetRamDrivePath() {
    return NPrivate::GetTestEnv().RamDrivePath;
}

const TString& GetYtHddPath() {
    return NPrivate::GetTestEnv().YtHddPath;
}

const TString& GetOutputRamDrivePath() {
    return NPrivate::GetTestEnv().TestOutputRamDrivePath;
}

const TString& GdbPath() {
    return NPrivate::GetTestEnv().GdbPath;
}

const TString& GetTestParam(TStringBuf name) {
    const static TString def = "";
    return GetTestParam(name, def);
}

const TString& GetTestParam(TStringBuf name, const TString& def) {
    auto& testParameters = NPrivate::GetTestEnv().TestParameters;
    auto it = testParameters.find(name.data());
    if (it != testParameters.end()) {
        return it->second;
    }
    return def;
}

void AddEntryToCoreSearchFile(const TString& filename, TStringBuf cmd, int pid, const TFsPath& binaryPath = TFsPath(), const TFsPath& cwd = TFsPath()) {
    auto lock = TFileLock(filename);
    TGuard<TFileLock> guard(lock);

    TOFStream output(TFile(filename, WrOnly | ForAppend | OpenAlways));

    NJson::TJsonWriter writer(&output, false);
    writer.OpenMap();
    writer.Write("cmd", cmd);
    writer.Write("pid", pid);
    if (binaryPath) {
        writer.Write("binary_path", binaryPath);
    }
    if (cwd) {
        writer.Write("cwd", cwd);
    }
    writer.CloseMap();
    writer.Flush();

    output.Write("\n");
}

void WatchProcessCore(int pid, const TFsPath& binaryPath, const TFsPath& cwd) {
    auto& filename = NPrivate::GetTestEnv().CoreSearchFile;
    if (filename) {
        AddEntryToCoreSearchFile(filename, "add", pid, binaryPath, cwd);
    }
}

void StopProcessCoreWatching(int pid) {
    auto& filename = NPrivate::GetTestEnv().CoreSearchFile;
    if (filename) {
        AddEntryToCoreSearchFile(filename, "drop", pid);
    }
}

bool FromYaTest() {
    return NPrivate::GetTestEnv().IsRunningFromTest;
}

namespace NPrivate {
    TTestEnv::TTestEnv() {
        ReInitialize();
    }

    void TTestEnv::ReInitialize() {
        IsRunningFromTest = false;
        ArcadiaTestsDataDir = "";
        SourceRoot = "";
        BuildRoot = "";
        WorkPath = "";
        RamDrivePath = "";
        YtHddPath = "";
        TestOutputRamDrivePath = "";
        GdbPath = "";
        CoreSearchFile = "";
        TestParameters.clear();

        const TString contextFilename = GetEnv("YA_TEST_CONTEXT_FILE");
        if (contextFilename) {
            NJson::TJsonValue context;
            NJson::ReadJsonTree(TFileInput(contextFilename).ReadAll(), &context);

            NJson::TJsonValue* value;

            value = context.GetValueByPath("runtime.source_root");
            if (value) {
                SourceRoot = value->GetStringSafe("");
            }

            value = context.GetValueByPath("runtime.build_root");
            if (value) {
                BuildRoot = value->GetStringSafe("");
            }

            value = context.GetValueByPath("runtime.atd_root");
            if (value) {
                ArcadiaTestsDataDir = value->GetStringSafe("");
            }

            value = context.GetValueByPath("runtime.work_path");
            if (value) {
                WorkPath = value->GetStringSafe("");
            }

            value = context.GetValueByPath("runtime.ram_drive_path");
            if (value) {
                RamDrivePath = value->GetStringSafe("");
            }

            value = context.GetValueByPath("runtime.yt_hdd_path");
            if (value) {
                YtHddPath = value->GetStringSafe("");
            }

            value = context.GetValueByPath("runtime.test_output_ram_drive_path");
            if (value) {
                TestOutputRamDrivePath = value->GetStringSafe("");
            }

            value = context.GetValueByPath("runtime.gdb_bin");
            if (value) {
                GdbPath = value->GetStringSafe("");
            }

            value = context.GetValueByPath("runtime.test_params");
            if (value) {
                for (const auto& entry : context.GetValueByPath("runtime.test_params")->GetMap()) {
                    TestParameters[entry.first] = entry.second.GetStringSafe("");
                }
            }

            value = context.GetValueByPath("internal.core_search_file");
            if (value) {
                CoreSearchFile = value->GetStringSafe("");
            }
        }

        if (!YtHddPath) {
            YtHddPath = GetEnv("HDD_PATH");
        }

        if (!SourceRoot) {
            SourceRoot = GetEnv("ARCADIA_SOURCE_ROOT");
        }

        if (!BuildRoot) {
            BuildRoot = GetEnv("ARCADIA_BUILD_ROOT");
        }

        if (!ArcadiaTestsDataDir) {
            ArcadiaTestsDataDir = GetEnv("ARCADIA_TESTS_DATA_DIR");
        }

        if (!WorkPath) {
            WorkPath = GetEnv("TEST_WORK_PATH");
        }

        if (!RamDrivePath) {
            RamDrivePath = GetEnv("YA_TEST_RAM_DRIVE_PATH");
        }

        if (!TestOutputRamDrivePath) {
            TestOutputRamDrivePath = GetEnv("YA_TEST_OUTPUT_RAM_DRIVE_PATH");
        }

        const TString fromEnv = GetEnv("YA_TEST_RUNNER");
        IsRunningFromTest = (fromEnv == "1");
    }

    void TTestEnv::AddTestParam(TStringBuf name, TStringBuf value) {
        TestParameters[TString{name}] = value;
    }

    TString GetCwd() {
        try {
            return NFs::CurrentWorkingDirectory();
        } catch (...) {
            return {};
        }
    }

    const TTestEnv& GetTestEnv() {
        return *Singleton<TTestEnv>();
    }
}
