#pragma once

#include "types.h"
#include "backend.h"

#include <condition_variable>
#include <cmath>
#include <list>
#include <memory>
#include <mutex>
#include <shared_mutex>
#include <unordered_map>
#include <unordered_set>

extern "C" {
#include <libavcodec/avcodec.h>
#include <libavformat/avformat.h>
#include <libavutil/avutil.h>
#include <libavutil/dict.h>
#include <libavutil/display.h>
#include <libavutil/error.h>
#include <libavutil/frame.h>
#include <libavutil/imgutils.h>
#include <libavutil/opt.h>
#include <libavutil/pixdesc.h>
#include <libswscale/swscale.h>
}

namespace mediadb {

struct PreviewKey {
    std::string media_id;
    int width = 0;
    int height = 0;
    std::string fmt;
    int t_seconds = 0;
    int duration = 0;

    bool operator==(const PreviewKey& other) const {
        return media_id == other.media_id && width == other.width && height == other.height && fmt == other.fmt &&
               t_seconds == other.t_seconds && duration == other.duration;
    }
};

struct PreviewKeyHash {
    std::size_t operator()(const PreviewKey& key) const noexcept {
        std::size_t h = std::hash<std::string>{}(key.media_id);
        auto mix = [&](std::size_t v) {
            h ^= v + 0x9e3779b97f4a7c15ULL + (h << 6U) + (h >> 2U);
        };
        mix(std::hash<int>{}(key.width));
        mix(std::hash<int>{}(key.height));
        mix(std::hash<std::string>{}(key.fmt));
        mix(std::hash<int>{}(key.t_seconds));
        mix(std::hash<int>{}(key.duration));
        return h;
    }
};

struct PreviewValue {
    std::vector<std::uint8_t> bytes;
    std::string mime;
    std::string etag;
    std::size_t size = 0;
};

class PreviewCache {
public:
    explicit PreviewCache(std::size_t max_bytes);
    std::shared_ptr<const PreviewValue> get(const PreviewKey& key);
    void put(const PreviewKey& key, PreviewValue value);

private:
    struct CacheEntry {
        std::shared_ptr<const PreviewValue> value;
        std::list<PreviewKey>::iterator lru_it;
    };

    std::size_t max_bytes_;
    std::size_t used_bytes_ = 0;
    mutable std::shared_mutex mutex_;
    std::list<PreviewKey> lru_;
    std::unordered_map<PreviewKey, CacheEntry, PreviewKeyHash> entries_;
};

class FFmpegPreviewer {
public:
    std::optional<PreviewValue> render(const std::vector<std::uint8_t>& file_data, const std::string& media_kind,
                                       int width, int height, const std::string& fmt, int t_seconds,
                                       int duration, std::string& error_out);

    // Streaming variant: reads data lazily via range reads instead of loading the full file
    std::optional<PreviewValue> render_streaming(MediaBackendApi& db, const std::string& media_id,
                                                  std::uint64_t media_size, const std::string& media_kind,
                                                  int width, int height, const std::string& fmt, int t_seconds,
                                                  int duration, std::string& error_out);

private:
    std::optional<PreviewValue> render_video_clip(
        AVFormatContext* format_ctx, int stream_index, AVCodecContext* dec_ctx,
        int src_w, int src_h, int out_w, int out_h,
        SwsContext* sws, AVFrame* scaled,
        int duration_seconds, const std::string& codec_tag, std::string& error_out);

    std::optional<PreviewValue> encode_frame(AVFrame* frame, const std::string& fmt, std::string& error_out);
};

class PreviewService {
public:
    explicit PreviewService(PreviewCache& cache, MediaBackendApi& db);
    std::shared_ptr<const PreviewValue> cache_get(const PreviewKey& key);
    std::shared_ptr<const PreviewValue> get_or_create(const MediaRecord& media,
                                              const std::vector<std::uint8_t>& file_data,
                                              int width, int height, const std::string& fmt,
                                              int t_seconds, int duration, std::string& error_out);
    // Streaming variant: avoids loading full file for video clips
    std::shared_ptr<const PreviewValue> get_or_create_streaming(const MediaRecord& media,
                                              int width, int height, const std::string& fmt,
                                              int t_seconds, int duration, std::string& error_out);

private:
    PreviewCache& cache_;
    MediaBackendApi& db_;
    FFmpegPreviewer ffmpeg_;
    std::mutex inflight_mutex_;
    std::condition_variable inflight_cv_;
    std::unordered_set<PreviewKey, PreviewKeyHash> inflight_;
};

} // namespace mediadb
