#include "server.h"

#ifndef _WIN32
#include <grp.h>
#include <pwd.h>
#include <unistd.h>
#endif

#include <algorithm>
#include <iostream>
#include <sstream>

namespace mediadb {

// ---- MediaConExt helpers ----

MediaConExt* getMediaExt(libhttppp::HttpRequest& req) {
    return static_cast<MediaConExt*>(req.extension.get());
}

MediaConExt* ensureMediaExt(libhttppp::HttpRequest& req) {
    if (!req.extension) {
        req.extension = {new MediaConExt{}, +[](void* p){ delete static_cast<MediaConExt*>(p); }};
    }
    return static_cast<MediaConExt*>(req.extension.get());
}

// ---- MediaHttpEvent ----

MediaHttpEvent::MediaHttpEvent(App& app, std::vector<netplus::socket*> sockets)
    : libhttppp::HttpEvent(std::move(sockets)), app_(app) {}

void MediaHttpEvent::RequestEvent(libhttppp::HttpRequest& curreq, const int /*tid*/, ULONG_PTR /*args*/) {
  try {
    HttpRequest mreq = convert_request(curreq);
    std::cerr << "[mediadb] RequestEvent: " << mreq.method << " " << mreq.path << std::endl;

    // For upload endpoint: PUT with metadata in headers, or POST with multipart/form-data
    if ((mreq.method == "PUT" || mreq.method == "POST") && mreq.path == "/api/upload") {
        try {
            UploadData upload;

            auto ct_it = mreq.headers.find("content-type");
            std::string ct = ct_it != mreq.headers.end() ? ct_it->second : "";

            if (ct.find("multipart/form-data") != std::string::npos) {
                std::string boundary = extract_boundary(ct);
                auto parts = parse_multipart(mreq.body, boundary);
                for (const auto& part : parts) {
                    if (part.name == "album_id") {
                        upload.album_id = part.data;
                    } else if (part.name == "file") {
                        upload.filename = part.filename;
                        upload.file_data.assign(
                            reinterpret_cast<const std::uint8_t*>(part.data.data()),
                            reinterpret_cast<const std::uint8_t*>(part.data.data()) + part.data.size());
                    }
                }
            } else {
                auto it_album = mreq.headers.find("x-album-id");
                if (it_album != mreq.headers.end())
                    upload.album_id = it_album->second;

                auto it_fname = mreq.headers.find("x-filename");
                if (it_fname != mreq.headers.end())
                    upload.filename = it_fname->second;

                if (!curreq.RecvData.empty()) {
                    const char* p = curreq.RecvData.data();
                    size_t sz = curreq.RecvData.size();
                    upload.file_data.assign(
                        reinterpret_cast<const std::uint8_t*>(p),
                        reinterpret_cast<const std::uint8_t*>(p) + sz);
                } else if (!mreq.body.empty()) {
                    upload.file_data.assign(
                        reinterpret_cast<const std::uint8_t*>(mreq.body.data()),
                        reinterpret_cast<const std::uint8_t*>(mreq.body.data()) + mreq.body.size());
                }
            }

            HttpResponse mresp = app_.handle_upload_form(mreq, std::move(upload));
            send_response(curreq, mresp);
        } catch (libhttppp::HTTPException& e) {
            std::cerr << "upload form parse error: " << e.what() << "\n";
            HttpResponse mresp = HttpResponse::text(400, "multipart parse error");
            send_response(curreq, mresp);
        } catch (std::exception& e) {
            std::cerr << "upload error: " << e.what() << "\n";
            HttpResponse mresp = HttpResponse::text(500, std::string("upload error: ") + e.what());
            send_response(curreq, mresp);
        }
        return;
    }

    // For import endpoint
    if (mreq.method == "PUT" && mreq.path == "/api/import") {
        if (!app_.is_authorized(mreq)) {
            send_response(curreq, HttpResponse::json(401,
                [](){ auto*j=json_object_new_object(); json_object_object_add(j, "error", json_object_new_string("unauthorized")); return j; }()));
            return;
        }
        auto data = std::make_shared<std::vector<std::uint8_t>>();
        if (!curreq.RecvData.empty()) {
            const char* p = curreq.RecvData.data();
            data->assign(reinterpret_cast<const std::uint8_t*>(p),
                         reinterpret_cast<const std::uint8_t*>(p) + curreq.RecvData.size());
        } else if (!mreq.body.empty()) {
            data->assign(reinterpret_cast<const std::uint8_t*>(mreq.body.data()),
                         reinterpret_cast<const std::uint8_t*>(mreq.body.data()) + mreq.body.size());
        }
        std::cerr << "[import] raw body size=" << data->size() << "\n";
        HttpResponse mresp = app_.start_import(std::move(data));
        send_response(curreq, mresp);
        return;
    }

    HttpResponse mresp = app_.handle(mreq);
    send_response(curreq, mresp);
  } catch (netplus::NetException& e) {
    std::cerr << "RequestEvent net error: " << e.what() << "\n";
    try {
        HttpResponse mresp = HttpResponse::text(500, "internal server error");
        send_response(curreq, mresp);
    } catch (...) {}
  } catch (libhttppp::HTTPException& e) {
    std::cerr << "RequestEvent http error: " << e.what() << "\n";
    try {
        HttpResponse mresp = HttpResponse::text(500, "internal server error");
        send_response(curreq, mresp);
    } catch (...) {}
  } catch (std::exception& e) {
    std::cerr << "RequestEvent unhandled error: " << e.what() << "\n";
    try {
        HttpResponse mresp = HttpResponse::text(500, "internal server error");
        send_response(curreq, mresp);
    } catch (...) {}
  } catch (...) {
    std::cerr << "RequestEvent unknown error" << "\n";
    try {
        HttpResponse mresp = HttpResponse::text(500, "internal server error");
        send_response(curreq, mresp);
    } catch (...) {}
  }
}

void MediaHttpEvent::ResponseEvent(libhttppp::HttpRequest& curreq, const int /*tid*/, ULONG_PTR /*args*/) {
    auto* ext = getMediaExt(curreq);
    if (!ext || ext->send_size == 0) return;
    if (ext->produced >= ext->send_size) return;

    static constexpr size_t MAX_FILL = BLOCKSIZE * 16;
    size_t filled = 0;
    while (filled < MAX_FILL && ext->produced < ext->send_size) {
        const size_t remaining = ext->send_size - ext->produced;
        const size_t chunk = remaining < BLOCKSIZE ? remaining : BLOCKSIZE;
        curreq.SendData.append(
            reinterpret_cast<const char*>(ext->data.data() + ext->produced), chunk);
        ext->produced += chunk;
        filled += chunk;
    }

    if (ext->produced >= ext->send_size) {
        ext->data.clear();
        ext->data.shrink_to_fit();
    }
}

void MediaHttpEvent::ConnectEvent(libhttppp::HttpRequest& /*curreq*/, const int /*tid*/, ULONG_PTR /*args*/) {}
void MediaHttpEvent::DisconnectEvent(libhttppp::HttpRequest& /*curreq*/, const int /*tid*/, ULONG_PTR /*args*/) {}

std::unordered_map<std::string, std::string> MediaHttpEvent::parse_query(const std::string& raw_url) {
    std::unordered_map<std::string, std::string> result;
    auto qpos = raw_url.find('?');
    if (qpos == std::string::npos) return result;
    std::string qs = raw_url.substr(qpos + 1);
    std::istringstream stream(qs);
    std::string pair;
    while (std::getline(stream, pair, '&')) {
        auto eq = pair.find('=');
        if (eq != std::string::npos) {
            result[pair.substr(0, eq)] = pair.substr(eq + 1);
        } else {
            result[pair] = "";
        }
    }
    return result;
}

HttpRequest MediaHttpEvent::convert_request(libhttppp::HttpRequest& curreq) {
    HttpRequest mreq;

    switch (curreq.getRequestType()) {
        case GETREQUEST:    mreq.method = "GET";    break;
        case POSTREQUEST:   mreq.method = "POST";   break;
        case PUTREQUEST:    mreq.method = "PUT";    break;
        case DELETEREQUEST: mreq.method = "DELETE"; break;
        default:            mreq.method = "GET";    break;
    }

    mreq.path = curreq.getRequestURL();
    mreq.query = parse_query(curreq.getRequest());

    for (auto* hd = curreq.getfirstHeaderData(); hd != nullptr; hd = hd->nextHeaderData()) {
        std::string key = hd->getkey();
        std::transform(key.begin(), key.end(), key.begin(),
                       [](unsigned char c) { return static_cast<char>(std::tolower(c)); });
        auto* val = hd->getfirstValue();
        if (val != nullptr) {
            std::string joined = val->getvalue();
            for (auto* v = val->nextvalue(); v; v = v->nextvalue()) {
                joined += "; ";
                joined += v->getvalue();
            }
            mreq.headers[key] = std::move(joined);
        }
    }

    if (curreq.getRequestType() == POSTREQUEST || curreq.getRequestType() == PUTREQUEST) {
        size_t content_length = curreq.getContentLength();
        if (content_length > 0 && curreq.RecvData.size() >= content_length) {
            const char* data = curreq.RecvData.data() + (curreq.RecvData.size() - content_length);
            mreq.body.assign(data, content_length);
        } else if (!curreq.RecvData.empty()) {
            mreq.body.assign(curreq.RecvData.data(), curreq.RecvData.size());
        }
    }

    return mreq;
}

std::string MediaHttpEvent::status_to_http_state(int status) {
    switch (status) {
        case 200: return HTTP200;
        case 201: return HTTP201;
        case 202: return HTTP202;
        case 204: return HTTP204;
        case 206: return HTTP206;
        case 400: return HTTP400;
        case 401: return HTTP401;
        case 403: return HTTP403;
        case 404: return HTTP404;
        case 405: return HTTP405;
        case 409: return HTTP409;
        case 416: return HTTP416;
        case 422: return HTTP422;
        case 500: return HTTP500;
        case 503: return HTTP503;
        default:  return HTTP500;
    }
}

void MediaHttpEvent::send_response(libhttppp::HttpRequest& curreq, const HttpResponse& mresp) {
    std::cerr << "[mediadb] send_response: status=" << mresp.status
              << " content_type=" << mresp.content_type
              << " body_size=" << mresp.body.size() << std::endl;
    libhttppp::HttpResponse resp;
    resp.setState(status_to_http_state(mresp.status));
    resp.setContentType(mresp.content_type);
    resp.setContentLength(mresp.body.size());
    resp.setConnection("keep-alive");

    for (const auto& [key, value] : mresp.headers) {
        auto* hd = resp.setHeaderData(key);
        hd->push_back(value);
    }

    size_t sendDataBefore = curreq.SendData.size();
    const std::string empty_body;
    resp.send(curreq, empty_body, -1);
    {
        size_t hdrLen = curreq.SendData.size() - sendDataBefore;
        std::string hdrStr(curreq.SendData.data() + sendDataBefore, hdrLen);
        std::cerr << "[mediadb] response header (" << hdrLen << " bytes):\n" << hdrStr << std::endl;
    }
    if (!mresp.body.empty()) {
        curreq.SendData.append(reinterpret_cast<const char*>(mresp.body.data()), mresp.body.size());
    }
    std::cerr << "[mediadb] SendData total: " << curreq.SendData.size() << " bytes" << std::endl;
}

// ---- HttpServer ----

HttpServer::HttpServer(App& app, const std::string& addr, int port, int max_connections,
                       const std::string& cert_path, const std::string& key_path,
                       bool foreground, const std::string& run_user)
    : app_(app), addr_(addr), port_(port), max_connections_(max_connections),
      cert_path_(cert_path), key_path_(key_path),
      foreground_(foreground), run_user_(run_user) {}

int HttpServer::run() {
    try {
        libhttppp::HttpD httpd(addr_, port_, max_connections_, cert_path_, key_path_);
        auto sockets = httpd.getServerSockets();

        const char* proto = (!cert_path_.empty() && !key_path_.empty()) ? "https" : "http";

        if (!foreground_) {
            pid_t pid = fork();
            if (pid < 0) {
                std::cerr << "fork() failed: " << strerror(errno) << "\n";
                return 1;
            }
            if (pid > 0) {
                std::cout << "mediadb started on " << proto << "://" << addr_ << ":" << port_
                          << " (pid " << pid << ")\n";
                _exit(0);
            }
            if (setsid() < 0) {
                std::cerr << "setsid() failed: " << strerror(errno) << "\n";
                return 1;
            }
            freopen("/dev/null", "r", stdin);
            freopen("/dev/null", "w", stdout);
            freopen("/dev/null", "w", stderr);
        } else {
            std::cout << "mediadb started on " << proto << "://" << addr_ << ":" << port_ << "\n";
        }

        MediaHttpEvent event(app_, sockets);
        event.threads = std::max(event.threads * 4, 16);

        if (!run_user_.empty()) {
            drop_privileges(run_user_);
        }

        event.runEventloop();
    } catch (libhttppp::HTTPException& e) {
        std::cerr << "HTTP error: " << e.what() << "\n";
        return 1;
    } catch (std::exception& e) {
        std::cerr << "Error: " << e.what() << "\n";
        return 1;
    }
    return 0;
}

void HttpServer::drop_privileges(const std::string& username) {
    if (getuid() == 0) {
        struct passwd* pw = getpwnam(username.c_str());
        if (pw == nullptr) {
            throw std::runtime_error("User '" + username + "' not found");
        }
        if (initgroups(username.c_str(), pw->pw_gid) != 0) {
            throw std::runtime_error("initgroups() failed: " + std::string(strerror(errno)));
        }
        if (setgid(pw->pw_gid) != 0) {
            throw std::runtime_error("setgid() failed: " + std::string(strerror(errno)));
        }
        if (setuid(pw->pw_uid) != 0) {
            throw std::runtime_error("setuid() failed: " + std::string(strerror(errno)));
        }
    } else {
        std::cerr << "Warning: not running as root, cannot drop privileges to '" << username << "'\n";
    }
}

} // namespace mediadb
