#include "HttpFileImpl.h"
#include "HttpAppFrameworkImpl.h"
#include <drogon/MultiPart.h>
#include <drogon/utils/Utilities.h>
#include <fstream>
#include <iostream>
#include <algorithm>
#include <filesystem>
using namespace drogon;
int HttpFileImpl::save() const noexcept
{
    return save(HttpAppFrameworkImpl::instance().getUploadPath());
}
int HttpFileImpl::save(const std::string &path) const noexcept
{
    assert(!path.empty());
    if (fileName_.empty())
        return -1;
    std::filesystem::path fsUploadDir(utils::toNativePath(path));
    if (fsUploadDir.is_absolute())
    {  
    }
    else if ((!fsUploadDir.has_parent_path() ||
              (fsUploadDir.begin()->string() != "." &&
               fsUploadDir.begin()->string() != "..")))
    {
        fsUploadDir = utils::toNativePath(
                          HttpAppFrameworkImpl::instance().getUploadPath()) /
                      fsUploadDir;
    }
    else
    {
        fsUploadDir = std::filesystem::current_path() / fsUploadDir;
    }
    fsUploadDir = std::filesystem::weakly_canonical(fsUploadDir);
    if (!std::filesystem::exists(fsUploadDir))
    {
        LOG_TRACE << "create path:" << fsUploadDir;
        std::error_code err;
        std::filesystem::create_directories(fsUploadDir, err);
        if (err)
        {
            LOG_SYSERR;
            return -1;
        }
    }
    std::filesystem::path fsSaveToPath(std::filesystem::weakly_canonical(
        fsUploadDir / utils::toNativePath(fileName_)));
    LOG_TRACE << "save to path:" << fsSaveToPath;
    if (!std::equal(fsUploadDir.begin(),
                    fsUploadDir.end(),
                    fsSaveToPath.begin()))
    {
        LOG_ERROR
            << "Attempt writing outside of upload directory detected. Path: "
            << fileName_;
        return -1;
    }
    return saveTo(fsSaveToPath);
}
int HttpFileImpl::saveAs(const std::string &fileName) const noexcept
{
    assert(!fileName.empty());
    std::filesystem::path fsFileName(utils::toNativePath(fileName));
    if (!fsFileName.is_absolute() && (!fsFileName.has_parent_path() ||
                                      (fsFileName.begin()->string() != "." &&
                                       fsFileName.begin()->string() != "..")))
    {
        std::filesystem::path fsUploadPath(utils::toNativePath(
            HttpAppFrameworkImpl::instance().getUploadPath()));
        fsFileName = fsUploadPath / fsFileName;
    }
    if (fsFileName.has_parent_path() &&
        !std::filesystem::exists(fsFileName.parent_path()))
    {
        LOG_TRACE << "create path:" << fsFileName.parent_path();
        std::error_code err;
        std::filesystem::create_directories(fsFileName.parent_path(), err);
        if (err)
        {
            LOG_SYSERR;
            return -1;
        }
    }
    return saveTo(fsFileName);
}
int HttpFileImpl::saveTo(
    const std::filesystem::path &pathAndFileName) const noexcept
{
    LOG_TRACE << "save uploaded file:" << pathAndFileName;
    auto wPath = utils::toNativePath(pathAndFileName.native());
    std::ofstream file(wPath, std::ios::binary);
    if (file.is_open())
    {
        file.write(fileContent_.data(), fileContent_.size());
        file.close();
        return 0;
    }
    else
    {
        LOG_ERROR << "save failed!";
        return -1;
    }
}
std::string HttpFileImpl::getMd5() const noexcept
{
    return utils::getMd5(fileContent_.data(), fileContent_.size());
}
std::string HttpFileImpl::getSha256() const noexcept
{
    return utils::getSha256(fileContent_.data(), fileContent_.size());
}
std::string HttpFileImpl::getSha3() const noexcept
{
    return utils::getSha3(fileContent_.data(), fileContent_.size());
}
const std::string &HttpFile::getFileName() const noexcept
{
    return implPtr_->getFileName();
}
void HttpFile::setFileName(const std::string &fileName) noexcept
{
    implPtr_->setFileName(fileName);
}
std::string_view HttpFile::getFileExtension() const noexcept
{
    return implPtr_->getFileExtension();
}
FileType HttpFile::getFileType() const noexcept
{
    return implPtr_->getFileType();
}
void HttpFile::setFile(const char *data, size_t length) noexcept
{
    implPtr_->setFile(data, length);
}
int HttpFile::save() const noexcept
{
    return implPtr_->save();
}
int HttpFile::save(const std::string &path) const noexcept
{
    return implPtr_->save(path);
}
int HttpFile::saveAs(const std::string &fileName) const noexcept
{
    return implPtr_->saveAs(fileName);
}
size_t HttpFile::fileLength() const noexcept
{
    return implPtr_->fileLength();
}
drogon::ContentType HttpFile::getContentType() const noexcept
{
    return implPtr_->getContentType();
}
const char *HttpFile::fileData() const noexcept
{
    return implPtr_->fileData();
}
std::string HttpFile::getMd5() const noexcept
{
    return implPtr_->getMd5();
}
const std::string &HttpFile::getContentTransferEncoding() const noexcept
{
    return implPtr_->getContentTransferEncoding();
}
HttpFile::HttpFile(std::shared_ptr<HttpFileImpl> &&implPtr) noexcept
    : implPtr_(std::move(implPtr))
{
}
const std::string &HttpFile::getItemName() const noexcept
{
    return implPtr_->getItemName();
}