#include "media/dimension_filter.hpp"

#include "media/edit_export.hpp"
#include "media/ffmpeg_raii.hpp"

#include <algorithm>
#include <cstdint>
#include <limits>
#include <map>
#include <sstream>
#include <utility>
#include <vector>

#ifdef SMARTCUT_HAS_FFMPEG
extern "C" {
#include <libavformat/avformat.h>
}
#endif

namespace smartcut::media {
namespace {

struct DimensionFrame { int width{}; int height{}; std::int64_t time_us{}; };

#ifdef SMARTCUT_HAS_FFMPEG
std::vector<DimensionFrame> decode_dimensions(const std::filesystem::path& input, std::string& error) {
  auto format = ffmpeg::open_input(input);
  if (!format || avformat_find_stream_info(format.get(), nullptr) < 0) { error = "input failed"; return {}; }
  int video_stream = -1;
  for (unsigned index = 0; index < format->nb_streams; ++index)
    if (format->streams[index]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO &&
        (format->streams[index]->disposition & AV_DISPOSITION_ATTACHED_PIC) == 0) { video_stream = static_cast<int>(index); break; }
  if (video_stream < 0) { error = "video stream missing"; return {}; }
  const auto* decoder = ffmpeg::preferred_video_decoder(format->streams[video_stream]->codecpar->codec_id);
  auto context = ffmpeg::make_codec_context(decoder); auto packet = ffmpeg::make_packet(); auto frame = ffmpeg::make_frame();
  if (!context || !packet || !frame || avcodec_parameters_to_context(context.get(), format->streams[video_stream]->codecpar) < 0 || avcodec_open2(context.get(), decoder, nullptr) < 0) { error = "decoder failed"; return {}; }
  std::vector<DimensionFrame> frames;
  const auto receive = [&] { while (avcodec_receive_frame(context.get(), frame.get()) >= 0) { const auto pts = frame->best_effort_timestamp != AV_NOPTS_VALUE ? frame->best_effort_timestamp : frame->pts; frames.push_back({frame->width, frame->height, pts == AV_NOPTS_VALUE ? static_cast<std::int64_t>(frames.size()) : av_rescale_q(pts, format->streams[video_stream]->time_base, AV_TIME_BASE_Q)}); av_frame_unref(frame.get()); } };
  while (av_read_frame(format.get(), packet.get()) >= 0) { if (packet->stream_index == video_stream && avcodec_send_packet(context.get(), packet.get()) >= 0) receive(); av_packet_unref(packet.get()); }
  avcodec_send_packet(context.get(), nullptr); receive();
  if (frames.empty()) error = "no decoded video frames";
  return frames;
}
#endif

bool report_passed(const std::string& report) {
  const auto marker = report.find("\"status\":\"");
  return marker != std::string::npos && report.compare(marker + 10, 6, "PASSED") == 0;
}

}  // namespace

std::string scan_video_dimensions(const std::filesystem::path& input, double sample_interval_seconds) {
#ifndef SMARTCUT_HAS_FFMPEG
  (void)input; (void)sample_interval_seconds;
  return "{\"schema\":\"dimension-scan-v1\",\"status\":\"FAILED\",\"error\":\"FFmpeg unavailable\"}";
#else
  std::string error; const auto frames = decode_dimensions(input, error);
  if (!error.empty()) return "{\"schema\":\"dimension-scan-v1\",\"status\":\"FAILED\",\"error\":\"" + error + "\"}";
  struct Summary { std::size_t samples{}; std::size_t frames{}; std::size_t first{std::numeric_limits<std::size_t>::max()}; std::size_t last{}; };
  std::map<std::pair<int, int>, Summary> summaries;
  const auto interval_us = static_cast<std::int64_t>(std::max(0.0, sample_interval_seconds) * 1000000.0);
  std::int64_t next_sample = std::numeric_limits<std::int64_t>::min();
  for (std::size_t index = 0; index < frames.size(); ++index) { auto& summary = summaries[{frames[index].width, frames[index].height}]; ++summary.frames; summary.first = std::min(summary.first, index); summary.last = index; if (interval_us <= 0 || frames[index].time_us >= next_sample) { ++summary.samples; next_sample = frames[index].time_us + interval_us; } }
  std::ostringstream report; report << "{\"schema\":\"dimension-scan-v1\",\"status\":\"PASSED\",\"sample_interval_seconds\":" << sample_interval_seconds << ",\"frame_count\":" << frames.size() << ",\"dimensions\":["; bool first = true;
  for (const auto& [dimension, summary] : summaries) { if (!first) report << ','; first = false; report << "{\"width\":" << dimension.first << ",\"height\":" << dimension.second << ",\"sample_count\":" << summary.samples << ",\"frame_count\":" << summary.frames << ",\"first_frame\":" << summary.first << ",\"last_frame\":" << summary.last << '}'; }
  report << "]}"; return report.str();
#endif
}

std::string filter_video_dimensions(const std::filesystem::path& input, const std::filesystem::path& output,
                                    int width, int height) {
#ifndef SMARTCUT_HAS_FFMPEG
  (void)input; (void)output; (void)width; (void)height;
  return "{\"schema\":\"dimension-filter-v1\",\"status\":\"FAILED\"}";
#else
  if (width <= 0 || height <= 0) return "{\"schema\":\"dimension-filter-v1\",\"status\":\"FAILED\",\"error\":\"invalid target dimensions\"}";
  std::string error; const auto frames = decode_dimensions(input, error);
  if (!error.empty()) return "{\"schema\":\"dimension-filter-v1\",\"status\":\"FAILED\",\"error\":\"" + error + "\"}";
  std::vector<EditRange> removed; std::size_t start = 0; bool removing = false; std::size_t kept = 0;
  for (std::size_t index = 0; index < frames.size(); ++index) { const bool match = frames[index].width == width && frames[index].height == height; if (match) { ++kept; if (removing) { removed.emplace_back(start, index); removing = false; } } else if (!removing) { start = index; removing = true; } }
  if (removing) removed.emplace_back(start, frames.size());
  if (kept == 0) return "{\"schema\":\"dimension-filter-v1\",\"status\":\"FAILED\",\"error\":\"target dimensions were not found\"}";
  const auto export_report = export_mkv_edits(input.string(), output.string(), frames.size(), removed, {}, EditExportMode::smart_render, 0);
  std::ostringstream report; report << "{\"schema\":\"dimension-filter-v1\",\"status\":\"" << (report_passed(export_report) ? "PASSED" : "FAILED") << "\",\"width\":" << width << ",\"height\":" << height << ",\"source_frames\":" << frames.size() << ",\"kept_frames\":" << kept << ",\"removed_ranges\":" << removed.size() << ",\"export_report\":" << export_report << '}'; return report.str();
#endif
}

}  // namespace smartcut::media
