#include <webserv/http/HttpRequest.hpp>

#include <webserv/config/ServerConfig.hpp>

#include <webserv/config/ConfigManager.hpp> // for ConfigManager
#include <webserv/handler/URI.hpp>          // for URI
#include <webserv/http/HttpConstants.hpp>   // for CRLF, DOUBLE_CRLF
#include <webserv/log/Log.hpp>     // for Log, LOCATION
#include <webserv/utils/utils.hpp> // for stoul

#include <exception> // for exception
#include <map>       // for map
#include <memory>    // for allocator, make_unique, unique_ptr
#include <optional>  // for optional
#include <sstream>   // for basic_stringstream, basic_istream, stringstream
#include <vector>    // for vector

HttpRequest::HttpRequest(Client *client) : client_(client), uri_(nullptr)
{
    Log::trace(LOCATION);
}

HttpRequest::~HttpRequest()
{
    Log::trace(LOCATION);
}

HttpRequest::State HttpRequest::getState() const noexcept
{
    return state_;
}

void HttpRequest::setState(State state)
{
    if (state == State::Complete)
    {
        ServerConfig *serverConfig = getServerConfig();
        if (serverConfig == nullptr)
        {
            Log::error("No matching server config found for host");
            state_ = State::ParseError;
            return;
        }
        uri_ = std::make_unique<URI>(*this, *serverConfig);
    }
    state_ = state;
}

const HttpHeaders &HttpRequest::getHeaders() const noexcept
{
    return headers_;
}

const std::string &HttpRequest::getBody() const noexcept
{
    return body_;
}

void HttpRequest::receiveData(const char *data, size_t length)
{
    Log::trace(LOCATION);
    buffer_.append(data, length);
    parseBuffer();
}

void HttpRequest::parseBuffer()
{
    Log::trace(LOCATION);
    while (true)
    {
        try
        {
            switch (state_)
            {
            case State::RequestLine:
                if (!parseBufferforRequestLine())
                {
                    return; // Wait for more data
                }
                break;
            case State::Headers:
                if (!parseBufferforHeaders())
                {
                    return; // Wait for more data
                }
                break;
            case State::Body:
                if (!parseBufferforBody())
                {
                    return; // Wait for more data
                }
                break;
            case State::Chunked:
                if (!parseBufferforChunkedBody())
                {
                    return; // Wait for more data
                }
                break;
            case State::Complete:
                Log::debug("HttpRequest::parseBuffer() request is complete");
                return; // Request is complete
            case State::ParseError: Log::warning("Parse error occurred, stopping further processing"); return;
            }
        }
        catch (...)
        {
            Log::error("Exception during parsing, marking request as ParseError");
            state_ = State::ParseError;
            return;
        }
    }
}

bool HttpRequest::parseBufferforRequestLine()
{
    Log::trace(LOCATION);
    size_t pos = buffer_.find(Http::Protocol::CRLF);
    if (pos == std::string::npos)
    {
        Log::debug("RequestLine waiting for more data");
        return false;
    }
    std::string requestLine_ = buffer_.substr(0, pos);

    buffer_.erase(0, pos + Http::Protocol::CRLF.size());
    state_ = State::Headers;

    std::vector<std::string> parts;
    std::string part;
    std::stringstream ss(requestLine_);
    while (ss >> part)
    {
        parts.push_back(part);
    }
    if (parts.size() != 3)
    {
        Log::warning("Invalid request line: " + requestLine_);
        state_ = State::ParseError; // Mark as complete to avoid further processing
        return true;
    }
    method_ = parts[0];
    target_ = parts[1];
    httpVersion_ = parts[2];
    Log::debug("Parsed Request Line: Method=" + method_ + " Target=" + target_ + " Version=" + httpVersion_);
    return true;
}

bool HttpRequest::parseBufferforHeaders()
{
    Log::trace(LOCATION);
    size_t pos = buffer_.find(Http::Protocol::DOUBLE_CRLF);
    if (pos == std::string::npos)
    {
        Log::debug("Headers waiting for more data: " + LOCATION);
        return false; // Wait for more data
    }
    headers_.parse(buffer_.substr(0, pos + Http::Protocol::CRLF.size()));
    buffer_.erase(0, pos + Http::Protocol::DOUBLE_CRLF.size());

    if (this->headers_.getContentLength().value_or(0) > 0)
    {
        state_ = State::Body;
        return true;
    }
    if (this->headers_.has("Transfer-Encoding") && this->headers_.get("Transfer-Encoding") == "chunked")
    {
        Log::debug("HttpRequest::parseBuffer() in state Headers with chunked encoding");
        state_ = State::Chunked;
        return true;
    }
    Log::debug("No body to read, marking request as complete");
    setState(State::Complete);
    return false; // No body to read
}

bool HttpRequest::parseBufferforChunkedBody()
{
    Log::trace(LOCATION);
    while (true)
    {
        size_t pos = buffer_.find(Http::Protocol::CRLF);
        if (pos == std::string::npos)
        {
            Log::debug("Chunked body waiting for more data: " + LOCATION);
            return false;
        }
        std::string chunkSizeStr = buffer_.substr(0, pos);
        Log::debug("Chunk size string: " + chunkSizeStr);
        size_t chunkSize = 0;
        try
        {
            chunkSize = utils::stoul(chunkSizeStr, 16);
        }
        catch (const std::exception &e)
        {
            Log::warning("Invalid chunk size: " + chunkSizeStr + " - " + e.what());
            setState(State::ParseError);
            return false;
        }
        if (chunkSize == 0)
        {
            setState(State::Complete); // Last chunk
            buffer_.erase(0, pos + Http::Protocol::CRLF.size());
            return true;
        }
        if (buffer_.size() < pos + Http::Protocol::CRLF.size() + chunkSize + Http::Protocol::CRLF.size())
        {
            Log::debug("Chunked body waiting for more data: " + LOCATION);
            return false;
        }
        body_ += buffer_.substr(pos + Http::Protocol::CRLF.size(), chunkSize);
        buffer_.erase(0, pos + Http::Protocol::CRLF.size() + chunkSize + Http::Protocol::CRLF.size());
    }
    return true;
}

bool HttpRequest::parseBufferforBody()
{
    if (!headers_.getContentLength().has_value())
    {
        Log::warning("HttpRequest::parseBuffer() in state Body but no Content-Length header found");
        setState(State::Complete);
        return true;
    }
    Log::trace(LOCATION, {{"Content-Length", std::to_string(*headers_.getContentLength())}});
    if (buffer_.size() < *headers_.getContentLength())
    {
        Log::debug("Body waiting for more data: " + LOCATION);
        return false; // Wait for more data
    }
    body_ = buffer_.substr(0, *headers_.getContentLength());
    buffer_.erase(0, *headers_.getContentLength());
    setState(State::Complete);

    return true;
}

void HttpRequest::reset()
{
    Log::trace(LOCATION);
    state_ = State::RequestLine;
    buffer_.clear();
    // requestLine_.clear();
    // headers_.clear();
    body_.clear();
    // contentLength_ = 0;
}

ServerConfig *HttpRequest::getServerConfig() const
{
    std::string hostHeader = getHeaders().getHost().value_or("");
    if (hostHeader.empty())
    {
        return nullptr;
    }
    return ConfigManager::getInstance().getMatchingServerConfig(hostHeader);
}

const URI &HttpRequest::getUri() const noexcept
{
    return *uri_;
}

const std::string &HttpRequest::getMethod() const noexcept
{
    return method_;
}

const std::string &HttpRequest::getTarget() const noexcept
{
    return target_;
}

const std::string &HttpRequest::getHttpVersion() const noexcept
{
    return httpVersion_;
}

Client &HttpRequest::getClient() const noexcept
{
    return *client_;
}
