#pragma once

#include <algorithm>
#include <chrono>
#include <cstdint>
#include <cstring>
#include <filesystem>
#include <fstream>
#include <optional>
#include <string>
#include <unordered_map>
#include <vector>

#ifndef _WIN32
#include <fcntl.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <unistd.h>
#endif

extern "C" {
#include <json-c/json.h>
}

#include <uuidp.h>

namespace fs = std::filesystem;

namespace mediadb {

static inline std::string now_iso8601() {
    using namespace std::chrono;
    const auto now = system_clock::now();
    const auto tt = system_clock::to_time_t(now);
    std::tm tm{};
#if defined(_WIN32)
    gmtime_s(&tm, &tt);
#else
    gmtime_r(&tt, &tm);
#endif
    char buf[32]{};
    std::strftime(buf, sizeof(buf), "%Y-%m-%dT%H:%M:%SZ", &tm);
    return std::string(buf);
}

static inline std::string make_uuid() {
    uuid::uuid u;
    u.generate();
    return std::string(u.c_str());
}

// ---------- binary file helpers ----------

static inline void bin_write_u32(std::ostream& out, std::uint32_t v) {
    out.write(reinterpret_cast<const char*>(&v), 4);
}

static inline void bin_write_u64(std::ostream& out, std::uint64_t v) {
    out.write(reinterpret_cast<const char*>(&v), 8);
}

static inline void bin_write_str(std::ostream& out, const std::string& s) {
    auto len = static_cast<std::uint32_t>(s.size());
    bin_write_u32(out, len);
    if (len > 0) {
        out.write(s.data(), len);
    }
}

static inline std::uint32_t bin_read_u32(std::istream& in) {
    std::uint32_t v = 0;
    in.read(reinterpret_cast<char*>(&v), 4);
    return v;
}

static inline std::uint64_t bin_read_u64(std::istream& in) {
    std::uint64_t v = 0;
    in.read(reinterpret_cast<char*>(&v), 8);
    return v;
}

static inline std::string bin_read_str(std::istream& in) {
    auto len = bin_read_u32(in);
    if (len == 0) {
        return {};
    }
    std::string s(len, '\0');
    in.read(s.data(), len);
    return s;
}

// ---------- HTTP types ----------

struct HttpRequest {
    std::string method;
    std::string path;
    std::unordered_map<std::string, std::string> query;
    std::unordered_map<std::string, std::string> headers;
    std::string body;
};

struct HttpResponse {
    int status = 200;
    std::string content_type = "application/json";
    std::unordered_map<std::string, std::string> headers;
    std::vector<std::uint8_t> body;

    static HttpResponse json(int status, json_object* json) {
        HttpResponse resp;
        resp.status = status;
        resp.content_type = "application/json";
        resp.headers["Cache-Control"] = "no-store";
        const char* data = json_object_to_json_string(json);
        if (data != nullptr) {
            resp.body.assign(data, data + std::strlen(data));
        }
        json_object_put(json);
        return resp;
    }

    static HttpResponse text(int status, const std::string& text, const std::string& type = "text/plain; charset=utf-8") {
        HttpResponse resp;
        resp.status = status;
        resp.content_type = type;
        resp.body.assign(text.begin(), text.end());
        return resp;
    }
};

// ---------- data records ----------

struct MediaRecord {
    std::string id;
    std::string original_filename;
    std::string media_kind;
    std::string content_type;
    std::string created_at;
    std::uint64_t size_bytes = 0;
    std::string album_id;
    std::string store_id;
    std::uint64_t data_offset = 0;
    std::uint64_t record_offset = 0;   // MDS3: offset of the record header in the store file
    std::vector<std::uint8_t> pending_data;
};

struct AlbumRecord {
    std::string id;
    std::string name;
    std::string store_id;
    std::string created_at;
    bool is_public = false;
    std::uint64_t record_offset = 0;   // MDS3: offset of the record header in the store file
    std::vector<std::string> media_ids;
};

struct AuthDomain {
    std::string id;
    std::string label;
    std::string url;
    std::string client_name;
    std::string client_secret;
};

struct StoreACL {
    std::string id;
    std::string group_id;
    std::string group_name;
    bool can_read  = false;
    bool can_write = false;
};

struct StoreRecord {
    std::string id;
    std::string name;
    std::string created_at;
    std::vector<std::string> album_ids;
    std::vector<AuthDomain> auth_domains;
    std::vector<StoreACL>   acls;
};

// ---------- memory-mapped store file ----------

struct MmapRegion {
    void* addr = MAP_FAILED;
    std::size_t size = 0;

    MmapRegion() = default;
    MmapRegion(const MmapRegion&) = delete;
    MmapRegion& operator=(const MmapRegion&) = delete;
    MmapRegion(MmapRegion&& o) noexcept : addr(o.addr), size(o.size) { o.addr = MAP_FAILED; o.size = 0; }
    MmapRegion& operator=(MmapRegion&& o) noexcept { unmap(); addr = o.addr; size = o.size; o.addr = MAP_FAILED; o.size = 0; return *this; }
    ~MmapRegion() { unmap(); }

    bool valid() const { return addr != MAP_FAILED && size > 0; }
    const std::uint8_t* data() const { return static_cast<const std::uint8_t*>(addr); }

    void unmap() {
        if (addr != MAP_FAILED && size > 0) { ::munmap(addr, size); }
        addr = MAP_FAILED; size = 0;
    }

    static MmapRegion open(const fs::path& path) {
        MmapRegion r;
        int fd = ::open(path.c_str(), O_RDONLY);
        if (fd < 0) return r;
        struct stat st{};
        if (::fstat(fd, &st) != 0 || st.st_size <= 0) { ::close(fd); return r; }
        r.size = static_cast<std::size_t>(st.st_size);
        r.addr = ::mmap(nullptr, r.size, PROT_READ, MAP_PRIVATE, fd, 0);
        ::close(fd);
        if (r.addr == MAP_FAILED) { r.size = 0; }
        return r;
    }
};

// ---------- parsed upload data ----------

struct UploadData {
    std::string album_id;
    std::string filename;
    std::vector<std::uint8_t> file_data;
};

} // namespace mediadb
