#include "fgc/Paths.h"

#include "fgc/Logger.h"

#include <cstdio>
#include <cstdlib>
#include <ctime>
#include <filesystem>
#include <system_error>
#include <unistd.h>

namespace fs = std::filesystem;

namespace fgc::paths {

namespace {

std::string envOr(const char* name, const std::string& fallback) {
    const char* v = std::getenv(name);
    return (v && *v) ? std::string(v) : fallback;
}

}  // namespace

std::string expandUser(const std::string& path) {
    if (path.empty()) return path;

    std::string out;
    out.reserve(path.size());

    // Leading ~ -> $HOME
    size_t i = 0;
    if (path[0] == '~' && (path.size() == 1 || path[1] == '/')) {
        out += envOr("HOME", "");
        i = 1;
    }

    // $VAR and ${VAR} expansion
    for (; i < path.size(); ++i) {
        if (path[i] == '$') {
            size_t start = i + 1;
            bool braced = (start < path.size() && path[start] == '{');
            if (braced) ++start;
            size_t end = start;
            while (end < path.size() &&
                   (std::isalnum(static_cast<unsigned char>(path[end])) || path[end] == '_')) {
                ++end;
            }
            std::string name = path.substr(start, end - start);
            if (!name.empty()) {
                out += envOr(name.c_str(), "");
                i = braced && end < path.size() && path[end] == '}' ? end : end - 1;
                continue;
            }
        }
        out += path[i];
    }
    return out;
}

std::string executableDir() {
    std::error_code ec;
    fs::path self = fs::read_symlink("/proc/self/exe", ec);
    if (ec) return {};
    return self.parent_path().string();
}

std::vector<std::string> configSearchPaths(const std::string& cliArg) {
    std::vector<std::string> paths;
    if (!cliArg.empty()) paths.push_back(expandUser(cliArg));

    if (const char* env = std::getenv("FGC_CONFIG"); env && *env)
        paths.push_back(expandUser(env));

    paths.push_back("config.ini");  // current working directory

    if (std::string dir = executableDir(); !dir.empty())
        paths.push_back((fs::path(dir) / "config.ini").string());

    std::string xdg = envOr("XDG_CONFIG_HOME", expandUser("~/.config"));
    if (!xdg.empty())
        paths.push_back((fs::path(xdg) / "fire_gimbal_control" / "config.ini").string());

    return paths;
}

std::optional<std::string> resolveConfigPath(const std::string& cliArg) {
    std::error_code ec;
    for (const auto& p : configSearchPaths(cliArg)) {
        if (fs::is_regular_file(p, ec)) return p;
    }
    return std::nullopt;
}

std::string defaultOutputDir() {
    std::string base = envOr("XDG_DATA_HOME", expandUser("~/.local/share"));
    return (fs::path(base) / "fire_gimbal_control" / "images").string();
}

std::string defaultLogDir() {
    std::string base = envOr("XDG_DATA_HOME", expandUser("~/.local/share"));
    return (fs::path(base) / "fire_gimbal_control" / "logs").string();
}

std::string timestampedLogName(const std::string& prefix) {
    std::time_t t = std::time(nullptr);
    std::tm tm{};
    localtime_r(&t, &tm);
    char buf[32];
    std::strftime(buf, sizeof(buf), "%Y%m%d-%H%M%S", &tm);
    return prefix + "_" + buf + ".log";
}

std::string writeLogFile(const std::string& name, const std::string& text) {
    std::error_code ec;
    fs::path dir = defaultLogDir();
    fs::create_directories(dir, ec);
    fs::path path = dir / name;
    FILE* f = std::fopen(path.c_str(), "wb");
    if (!f) {
        LOG_WARN << "could not open logfile " << path.string() << " for writing";
        return "";
    }
    bool ok = std::fwrite(text.data(), 1, text.size(), f) == text.size();
    if (std::fclose(f) != 0) ok = false;
    if (!ok) {
        LOG_WARN << "failed to write logfile " << path.string();
        return "";
    }
    return path.string();
}

}  // namespace fgc::paths
