#include "core/pcm_diagnostics.h"

#include <sstream>
#include <string_view>
#include <utility>

namespace aptt {
namespace {

std::string EscapeJson(std::string_view value) {
  std::string escaped;
  escaped.reserve(value.size() + 8);
  for (const char ch : value) {
    switch (ch) {
      case '\\':
        escaped += "\\\\";
        break;
      case '"':
        escaped += "\\\"";
        break;
      case '\n':
        escaped += "\\n";
        break;
      case '\r':
        escaped += "\\r";
        break;
      case '\t':
        escaped += "\\t";
        break;
      default:
        escaped += ch;
        break;
    }
  }
  return escaped;
}

void AppendMetricIfUnsupported(std::vector<std::string>& unsupported,
                               bool supported,
                               const char* name) {
  if (!supported) {
    unsupported.push_back(name);
  }
}

std::string BoolJson(bool value) {
  return value ? "true" : "false";
}

std::string CsvValue(const std::map<std::string, std::string>& fields,
                     const char* key) {
  const auto found = fields.find(key);
  if (found == fields.end() || found->second.empty()) {
    return "unsupported";
  }
  return found->second;
}

void MergeRuntimeFields(PcmRunSummary& summary,
                        const DiagnosticEvent& event) {
  for (const auto& [key, value] : event.backend_fields) {
    if (key == "queue_depth" || key == "padding_frames" ||
        key == "available_frames" || key == "play_cursor_bytes" ||
        key == "write_cursor_bytes" || key == "lock_duration_ms" ||
        key == "buffer_frames" || key == "submitted_frames" ||
        key == "signal_mode" || key == "keep_alive_format" ||
        key == "dop_next_marker_frame" ||
        key == "buffer_index" ||
        key == "callback_duration_ms" ||
        key == "direct_process" ||
        key == "output_ready_called" ||
        key == "high_load_mode" ||
        key == "high_load_mechanism" ||
        key == "high_load_delay_ms" ||
        key == "high_load_delay_every_callbacks" ||
        key == "high_load_bounded" ||
        key == "late_callback" ||
        key == "late_callback_source") {
      summary.backend_fields[key] = value;
    }
  }
}

void MergeInitializationFields(PcmRunSummary& summary,
                               const DiagnosticEvent& event) {
  for (const auto& [key, value] : event.backend_fields) {
    if (key == "buffer_frames" || key == "buffer_count" ||
        key == "buffer_duration_ms" || key == "secondary_buffer_frames" ||
        key == "secondary_buffer_bytes" ||
        key == "secondary_buffer_duration_ms" ||
        key == "render_poll_interval_ms" ||
        key == "cursor_poll_interval_ms" ||
        key == "cooperative_level" ||
        key == "restore_lost_buffers" ||
        key == "signal_mode" ||
        key == "keep_alive_format" ||
        key == "dop_dsd_rate_multiplier" ||
        key == "dop_marker_first" ||
        key == "dop_dsd_byte_order" ||
        key == "pcm_carrier_sample_type" ||
        key == "buffer_min" ||
        key == "buffer_max" ||
        key == "buffer_preferred" ||
        key == "buffer_granularity" ||
        key == "input_latency" ||
        key == "output_latency" ||
        key == "output_ready_supported" ||
        key == "asio_sample_type" ||
        key == "sample_rate_set_result" ||
        key == "current_sample_rate" ||
        key == "repeated_asio_exit" ||
        key == "high_load_mode" ||
        key == "high_load_mechanism" ||
        key == "high_load_delay_ms" ||
        key == "high_load_delay_every_callbacks" ||
        key == "high_load_bounded" ||
        key == "high_load_risk_acknowledged") {
      summary.backend_fields[key] = value;
    }
  }
}

}  // namespace

PcmMetricAvailability PcmMetricAvailabilityForBackend(BackendApi backend) {
  PcmMetricAvailability availability;
  switch (backend) {
    case BackendApi::kMme:
      availability.queue_depth = true;
      break;
    case BackendApi::kWasapiShared:
    case BackendApi::kWasapiExclusive:
      availability.padding_frames = true;
      availability.device_invalidation = true;
      break;
    case BackendApi::kDirectSound:
      availability.play_write_cursor = true;
      availability.lock_unlock_timing = true;
      availability.lost_buffer_events = true;
      break;
    case BackendApi::kNull:
    case BackendApi::kAsio:
    case BackendApi::kKernelStreaming:
      break;
  }
  return availability;
}

PcmRunSummary BuildPcmRunSummary(BackendApi backend,
                                std::string device_id,
                                const std::vector<DiagnosticEvent>& events,
                                const RealtimeStats& final_stats) {
  PcmRunSummary summary;
  summary.backend = backend;
  summary.device_id = std::move(device_id);
  summary.common_counters = final_stats;
  summary.metrics = PcmMetricAvailabilityForBackend(backend);

  AppendMetricIfUnsupported(
      summary.unsupported_metrics,
      summary.metrics.queue_depth,
      "queue_depth");
  AppendMetricIfUnsupported(
      summary.unsupported_metrics,
      summary.metrics.padding_frames,
      "padding_frames");
  AppendMetricIfUnsupported(
      summary.unsupported_metrics,
      summary.metrics.play_write_cursor,
      "play_write_cursor");
  AppendMetricIfUnsupported(
      summary.unsupported_metrics,
      summary.metrics.lock_unlock_timing,
      "lock_unlock_timing");
  AppendMetricIfUnsupported(
      summary.unsupported_metrics,
      summary.metrics.lost_buffer_events,
      "lost_buffer_events");
  AppendMetricIfUnsupported(
      summary.unsupported_metrics,
      summary.metrics.device_invalidation,
      "device_invalidation");

  for (const auto& event : events) {
    if (event.lifecycle_to) {
      summary.final_lifecycle_state = *event.lifecycle_to;
    }
    if (event.type == DiagnosticEventType::kRuntimeStats) {
      MergeRuntimeFields(summary, event);
    } else if (event.type == DiagnosticEventType::kFormatProbe) {
      MergeInitializationFields(summary, event);
    } else if (event.type == DiagnosticEventType::kError) {
      const auto api = event.backend_fields.find("api");
      if (api != event.backend_fields.end() &&
          api->second.find("Restore") != std::string::npos) {
        summary.backend_fields["lost_buffer_restore_event"] = "true";
      }
    }
  }

  summary.backend_fields["auto_retry_attempted"] = "false";
  summary.backend_fields["auto_backend_switch_attempted"] = "false";
  return summary;
}

std::string SerializePcmRunSummaryJson(const PcmRunSummary& summary) {
  std::ostringstream out;
  out << "{\n";
  out << "  \"schema_version\": " << summary.schema_version << ",\n";
  out << "  \"backend\": \"" << ToString(summary.backend) << "\",\n";
  out << "  \"device_id\": \"" << EscapeJson(summary.device_id) << "\",\n";
  out << "  \"final_lifecycle_state\": ";
  if (summary.final_lifecycle_state) {
    out << '"' << ToString(*summary.final_lifecycle_state) << '"';
  } else {
    out << "null";
  }
  out << ",\n";
  out << "  \"common_counters\": {\n";
  out << "    \"frames_generated\": "
      << summary.common_counters.frames_generated << ",\n";
  out << "    \"frames_submitted\": "
      << summary.common_counters.frames_submitted << ",\n";
  out << "    \"frames_accepted\": "
      << summary.common_counters.frames_accepted << ",\n";
  out << "    \"callback_count\": "
      << summary.common_counters.callback_count << ",\n";
  out << "    \"underrun_count\": "
      << summary.common_counters.underrun_count << ",\n";
  out << "    \"discontinuity_count\": "
      << summary.common_counters.discontinuity_count << ",\n";
  out << "    \"device_invalidated_count\": "
      << summary.common_counters.device_invalidated_count << "\n";
  out << "  },\n";
  out << "  \"metric_availability\": {\n";
  out << "    \"queue_depth\": " << BoolJson(summary.metrics.queue_depth) << ",\n";
  out << "    \"padding_frames\": " << BoolJson(summary.metrics.padding_frames) << ",\n";
  out << "    \"play_write_cursor\": " << BoolJson(summary.metrics.play_write_cursor) << ",\n";
  out << "    \"lock_unlock_timing\": " << BoolJson(summary.metrics.lock_unlock_timing) << ",\n";
  out << "    \"lost_buffer_events\": " << BoolJson(summary.metrics.lost_buffer_events) << ",\n";
  out << "    \"device_invalidation\": " << BoolJson(summary.metrics.device_invalidation) << "\n";
  out << "  },\n";
  out << "  \"unsupported_metrics\": [";
  for (std::size_t index = 0; index < summary.unsupported_metrics.size(); ++index) {
    if (index != 0) {
      out << ", ";
    }
    out << '"' << EscapeJson(summary.unsupported_metrics[index]) << '"';
  }
  out << "],\n";
  out << "  \"auto_retry_attempted\": "
      << BoolJson(summary.auto_retry_attempted) << ",\n";
  out << "  \"auto_backend_switch_attempted\": "
      << BoolJson(summary.auto_backend_switch_attempted) << ",\n";
  out << "  \"backend_fields\": {";
  bool first = true;
  for (const auto& [key, value] : summary.backend_fields) {
    if (!first) {
      out << ", ";
    }
    first = false;
    out << '"' << EscapeJson(key) << "\": \"" << EscapeJson(value) << '"';
  }
  out << "}\n";
  out << "}\n";
  return out.str();
}

std::string SerializePcmComparisonCsv(
    const std::vector<PcmRunSummary>& summaries) {
  std::ostringstream out;
  out << "backend,device_id,frames_generated,frames_submitted,frames_accepted,"
         "callback_count,underrun_count,discontinuity_count,"
         "device_invalidated_count,queue_depth,padding_frames,"
         "play_cursor_bytes,write_cursor_bytes,lock_duration_ms,"
         "auto_retry_attempted,auto_backend_switch_attempted\n";
  for (const auto& summary : summaries) {
    out << ToString(summary.backend) << ','
        << '"' << EscapeJson(summary.device_id) << '"' << ','
        << summary.common_counters.frames_generated << ','
        << summary.common_counters.frames_submitted << ','
        << summary.common_counters.frames_accepted << ','
        << summary.common_counters.callback_count << ','
        << summary.common_counters.underrun_count << ','
        << summary.common_counters.discontinuity_count << ','
        << summary.common_counters.device_invalidated_count << ','
        << CsvValue(summary.backend_fields, "queue_depth") << ','
        << CsvValue(summary.backend_fields, "padding_frames") << ','
        << CsvValue(summary.backend_fields, "play_cursor_bytes") << ','
        << CsvValue(summary.backend_fields, "write_cursor_bytes") << ','
        << CsvValue(summary.backend_fields, "lock_duration_ms") << ','
        << (summary.auto_retry_attempted ? "true" : "false") << ','
        << (summary.auto_backend_switch_attempted ? "true" : "false") << '\n';
  }
  return out.str();
}

}  // namespace aptt
