#include "media/audio_timing.hpp"
#include <algorithm>
#include <cstdlib>
#include <limits>
#include <sstream>
#include <vector>
#ifdef SMARTCUT_HAS_FFMPEG
extern "C" {
#include <libavformat/avformat.h>
}
#endif
namespace smartcut::media {
std::string probe_audio_timing(const std::string& path, std::size_t max_packets) {
#ifndef SMARTCUT_HAS_FFMPEG
  (void)path; (void)max_packets; return "{\"error\":\"FFmpeg support is not enabled; configure with SMARTCUT_MEDIA_ROOT\"}";
#else
  AVFormatContext* format = nullptr; if (avformat_open_input(&format, path.c_str(), nullptr, nullptr) < 0) return "{\"error\":\"avformat_open_input failed\"}";
  if (avformat_find_stream_info(format, nullptr) < 0) { avformat_close_input(&format); return "{\"error\":\"avformat_find_stream_info failed\"}"; }
  int audio_stream = -1; for (unsigned i = 0; i < format->nb_streams; ++i) if (format->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_AUDIO) { audio_stream = static_cast<int>(i); break; }
  if (audio_stream < 0) { avformat_close_input(&format); return "{\"error\":\"no audio stream\"}"; }
  const auto* stream = format->streams[audio_stream]; AVPacket* packet = av_packet_alloc(); std::size_t count = 0; std::ostringstream output;
  output << "{\"schema\":\"audio-timing-v1\",\"stream_index\":" << audio_stream << ",\"time_base_num\":" << stream->time_base.num << ",\"time_base_den\":" << stream->time_base.den << ",\"packets\":[";
  while (packet && count < max_packets && av_read_frame(format, packet) >= 0) { if (packet->stream_index == audio_stream) { if (count != 0) output << ','; output << "{\"pts\":" << packet->pts << ",\"dts\":" << packet->dts << ",\"duration\":" << packet->duration << ",\"size\":" << packet->size << '}'; ++count; } av_packet_unref(packet); }
  output << "]}"; av_packet_free(&packet); avformat_close_input(&format); return output.str();
#endif
}

std::string select_audio_packets(const std::string& path, std::int64_t video_start_pts, std::int64_t video_end_pts) {
#ifndef SMARTCUT_HAS_FFMPEG
  (void)path; (void)video_start_pts; (void)video_end_pts; return "{\"error\":\"FFmpeg support is not enabled; configure with SMARTCUT_MEDIA_ROOT\"}";
#else
  if (video_end_pts <= video_start_pts) return "{\"error\":\"video end PTS must be greater than video start PTS\"}";
  AVFormatContext* format = nullptr; if (avformat_open_input(&format, path.c_str(), nullptr, nullptr) < 0 || avformat_find_stream_info(format, nullptr) < 0) { avformat_close_input(&format); return "{\"error\":\"input open failed\"}"; }
  int audio_stream = -1; for (unsigned i = 0; i < format->nb_streams; ++i) if (format->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_AUDIO) { audio_stream = static_cast<int>(i); break; }
  if (audio_stream < 0) { avformat_close_input(&format); return "{\"error\":\"audio stream not found\"}"; }
  const auto time_base = format->streams[audio_stream]->time_base; AVPacket* packet = av_packet_alloc(); std::uint64_t selected = 0; std::int64_t first_pts = AV_NOPTS_VALUE; std::int64_t last_end = AV_NOPTS_VALUE; while (packet && av_read_frame(format, packet) >= 0) { if (packet->stream_index == audio_stream && packet->pts != AV_NOPTS_VALUE) { const auto duration = packet->duration > 0 ? packet->duration : 1; const auto packet_end = packet->pts + duration; if (packet_end > video_start_pts && packet->pts < video_end_pts) { if (first_pts == AV_NOPTS_VALUE) first_pts = packet->pts; last_end = packet_end; ++selected; } } av_packet_unref(packet); }
  av_packet_free(&packet); avformat_close_input(&format); const auto leading = first_pts == AV_NOPTS_VALUE ? 0 : video_start_pts - first_pts; const auto trailing = last_end == AV_NOPTS_VALUE ? 0 : last_end - video_end_pts; std::ostringstream report; report << "{\"schema\":\"audio-selection-report-v1\",\"audio_stream\":" << audio_stream << ",\"time_base_num\":" << time_base.num << ",\"time_base_den\":" << time_base.den << ",\"video_start_pts\":" << video_start_pts << ",\"video_end_pts\":" << video_end_pts << ",\"selected_packets\":" << selected << ",\"source_first_pts\":" << (first_pts == AV_NOPTS_VALUE ? 0 : first_pts) << ",\"output_retime_first_pts\":0,\"leading_tolerance_pts\":" << leading << ",\"trailing_tolerance_pts\":" << trailing << ",\"policy\":\"complete-audio-frame-overlap-retimed-to-zero\"}"; return report.str();
#endif
}

std::string measure_av_drift(const std::string& path) {
#ifndef SMARTCUT_HAS_FFMPEG
  (void)path;
  return "{\"error\":\"FFmpeg support is not enabled\"}";
#else
  AVFormatContext* format = nullptr;
  if (avformat_open_input(&format, path.c_str(), nullptr, nullptr) < 0 || avformat_find_stream_info(format, nullptr) < 0) {
    avformat_close_input(&format);
    return "{\"error\":\"drift input open failed\"}";
  }
  int video_stream = -1;
  int audio_stream = -1;
  for (unsigned i = 0; i < format->nb_streams; ++i) {
    if (format->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO && video_stream < 0) video_stream = static_cast<int>(i);
    if (format->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_AUDIO && audio_stream < 0) audio_stream = static_cast<int>(i);
  }
  if (video_stream < 0 || audio_stream < 0) {
    avformat_close_input(&format);
    return "{\"error\":\"drift requires audio and video\"}";
  }
  const std::int64_t duration_us = std::max<std::int64_t>(format->duration, 0);
  constexpr std::int64_t interval_us = 600000000;
  std::vector<std::int64_t> targets{0};
  for (std::int64_t target = interval_us; target < duration_us; target += interval_us) targets.push_back(target);
  if (duration_us > 0 && targets.back() != duration_us) targets.push_back(duration_us);
  std::vector<std::int64_t> video_pts(targets.size(), AV_NOPTS_VALUE);
  std::vector<std::int64_t> audio_pts(targets.size(), AV_NOPTS_VALUE);
  std::vector<std::int64_t> video_distance(targets.size(), std::numeric_limits<std::int64_t>::max());
  std::vector<std::int64_t> audio_distance(targets.size(), std::numeric_limits<std::int64_t>::max());
  AVPacket* packet = av_packet_alloc();
  while (packet && av_read_frame(format, packet) >= 0) {
    if ((packet->stream_index == video_stream || packet->stream_index == audio_stream) && packet->pts != AV_NOPTS_VALUE) {
      const auto pts_us = av_rescale_q(packet->pts, format->streams[packet->stream_index]->time_base, AV_TIME_BASE_Q);
      auto& values = packet->stream_index == video_stream ? video_pts : audio_pts;
      auto& distances = packet->stream_index == video_stream ? video_distance : audio_distance;
      for (std::size_t i = 0; i < targets.size(); ++i) {
        const auto distance = std::llabs(pts_us - targets[i]);
        if (distance < distances[i]) { distances[i] = distance; values[i] = pts_us; }
      }
    }
    av_packet_unref(packet);
  }
  av_packet_free(&packet);
  avformat_close_input(&format);
  std::int64_t max_abs_drift = 0;
  bool complete = true;
  std::ostringstream report;
  report << "{\"schema\":\"av-drift-report-v1\",\"interval_us\":" << interval_us << ",\"duration_us\":" << duration_us << ",\"points\":[";
  for (std::size_t i = 0; i < targets.size(); ++i) {
    if (i) report << ',';
    const bool point_complete = video_pts[i] != AV_NOPTS_VALUE && audio_pts[i] != AV_NOPTS_VALUE;
    complete = complete && point_complete;
    const auto drift = point_complete ? audio_pts[i] - video_pts[i] : 0;
    max_abs_drift = std::max(max_abs_drift, std::llabs(drift));
    report << "{\"target_us\":" << targets[i] << ",\"video_pts_us\":" << (video_pts[i] == AV_NOPTS_VALUE ? 0 : video_pts[i])
           << ",\"audio_pts_us\":" << (audio_pts[i] == AV_NOPTS_VALUE ? 0 : audio_pts[i]) << ",\"drift_us\":" << drift << '}';
  }
  report << "],\"max_abs_drift_us\":" << max_abs_drift << ",\"complete\":" << (complete ? "true" : "false") << '}';
  return report.str();
#endif
}
}
