#include "core/diagnostic_log.h"

#include <charconv>
#include <sstream>

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 AppendStringField(std::ostringstream& out,
                       std::string_view key,
                       std::string_view value,
                       bool& first) {
  if (!first) {
    out << ',';
  }
  first = false;
  out << '"' << key << "\":\"" << EscapeJson(value) << '"';
}

void AppendUintField(std::ostringstream& out,
                     std::string_view key,
                     std::uint64_t value,
                     bool& first) {
  if (!first) {
    out << ',';
  }
  first = false;
  out << '"' << key << "\":" << value;
}

void AppendIntField(std::ostringstream& out,
                    std::string_view key,
                    int value,
                    bool& first) {
  if (!first) {
    out << ',';
  }
  first = false;
  out << '"' << key << "\":" << value;
}

std::optional<std::size_t> FindStringValueStart(const std::string& line,
                                                std::string_view key) {
  const std::string needle = "\"" + std::string(key) + "\":\"";
  const std::size_t pos = line.find(needle);
  if (pos == std::string::npos) {
    return std::nullopt;
  }
  return pos + needle.size();
}

std::optional<std::string> ExtractString(const std::string& line,
                                         std::string_view key) {
  const auto start = FindStringValueStart(line, key);
  if (!start) {
    return std::nullopt;
  }

  std::string value;
  bool escaping = false;
  for (std::size_t i = *start; i < line.size(); ++i) {
    const char ch = line[i];
    if (escaping) {
      switch (ch) {
        case 'n':
          value += '\n';
          break;
        case 'r':
          value += '\r';
          break;
        case 't':
          value += '\t';
          break;
        default:
          value += ch;
          break;
      }
      escaping = false;
      continue;
    }
    if (ch == '\\') {
      escaping = true;
      continue;
    }
    if (ch == '"') {
      return value;
    }
    value += ch;
  }

  return std::nullopt;
}

std::optional<std::uint64_t> ExtractUint(const std::string& line,
                                         std::string_view key) {
  const std::string needle = "\"" + std::string(key) + "\":";
  const std::size_t pos = line.find(needle);
  if (pos == std::string::npos) {
    return std::nullopt;
  }
  const std::size_t start = pos + needle.size();
  std::size_t end = start;
  while (end < line.size() && line[end] >= '0' && line[end] <= '9') {
    ++end;
  }
  std::uint64_t value = 0;
  const auto result = std::from_chars(line.data() + start, line.data() + end, value);
  if (result.ec != std::errc{}) {
    return std::nullopt;
  }
  return value;
}

std::optional<int> ExtractInt(const std::string& line, std::string_view key) {
  const auto value = ExtractUint(line, key);
  if (!value) {
    return std::nullopt;
  }
  return static_cast<int>(*value);
}

BackendApi BackendApiFromString(const std::string& value) {
  if (value == "MME") {
    return BackendApi::kMme;
  }
  if (value == "DirectSound") {
    return BackendApi::kDirectSound;
  }
  if (value == "WASAPI Shared") {
    return BackendApi::kWasapiShared;
  }
  if (value == "WASAPI Exclusive") {
    return BackendApi::kWasapiExclusive;
  }
  if (value == "ASIO") {
    return BackendApi::kAsio;
  }
  if (value == "Kernel Streaming") {
    return BackendApi::kKernelStreaming;
  }
  return BackendApi::kNull;
}

DeviceLifecycleState LifecycleStateFromString(const std::string& value) {
  if (value == "reserved") {
    return DeviceLifecycleState::kReserved;
  }
  if (value == "prepared") {
    return DeviceLifecycleState::kPrepared;
  }
  if (value == "running-signal") {
    return DeviceLifecycleState::kRunningSignal;
  }
  if (value == "running-silence") {
    return DeviceLifecycleState::kRunningSilence;
  }
  if (value == "draining") {
    return DeviceLifecycleState::kDraining;
  }
  if (value == "held-open") {
    return DeviceLifecycleState::kHeldOpen;
  }
  if (value == "releasing") {
    return DeviceLifecycleState::kReleasing;
  }
  if (value == "released") {
    return DeviceLifecycleState::kReleased;
  }
  if (value == "faulted") {
    return DeviceLifecycleState::kFaulted;
  }
  return DeviceLifecycleState::kDiscovered;
}

std::map<std::string, std::string> ExtractBackendFields(const std::string& line) {
  std::map<std::string, std::string> fields;
  const std::string needle = "\"backend_fields\":{";
  const std::size_t object_start = line.find(needle);
  if (object_start == std::string::npos) {
    return fields;
  }

  std::size_t pos = object_start + needle.size();
  while (pos < line.size() && line[pos] != '}') {
    while (pos < line.size() && (line[pos] == ',' || line[pos] == ' ')) {
      ++pos;
    }
    if (pos >= line.size() || line[pos] != '"') {
      break;
    }
    const std::size_t key_start = ++pos;
    const std::size_t key_end = line.find('"', key_start);
    if (key_end == std::string::npos) {
      break;
    }
    std::string key = line.substr(key_start, key_end - key_start);
    pos = key_end + 1;
    if (pos >= line.size() || line[pos] != ':') {
      break;
    }
    ++pos;
    if (pos >= line.size() || line[pos] != '"') {
      break;
    }
    const std::size_t value_start = ++pos;
    bool escaping = false;
    std::string value;
    for (; pos < line.size(); ++pos) {
      const char ch = line[pos];
      if (escaping) {
        value += ch;
        escaping = false;
        continue;
      }
      if (ch == '\\') {
        escaping = true;
        continue;
      }
      if (ch == '"') {
        break;
      }
      value += ch;
    }
    if (pos >= line.size()) {
      break;
    }
    ++pos;
    if (value_start <= line.size()) {
      fields.emplace(std::move(key), std::move(value));
    }
  }
  return fields;
}

}  // namespace

const char* ToString(DiagnosticEventType type) {
  switch (type) {
    case DiagnosticEventType::kSessionStart:
      return "session_start";
    case DiagnosticEventType::kLifecycleTransition:
      return "lifecycle_transition";
    case DiagnosticEventType::kFormatProbe:
      return "format_probe";
    case DiagnosticEventType::kRuntimeStats:
      return "runtime_stats";
    case DiagnosticEventType::kError:
      return "error";
    case DiagnosticEventType::kSessionSummary:
      return "session_summary";
  }
  return "unknown";
}

DiagnosticEventType DiagnosticEventTypeFromString(const std::string& value) {
  if (value == "lifecycle_transition") {
    return DiagnosticEventType::kLifecycleTransition;
  }
  if (value == "format_probe") {
    return DiagnosticEventType::kFormatProbe;
  }
  if (value == "runtime_stats") {
    return DiagnosticEventType::kRuntimeStats;
  }
  if (value == "error") {
    return DiagnosticEventType::kError;
  }
  if (value == "session_summary") {
    return DiagnosticEventType::kSessionSummary;
  }
  return DiagnosticEventType::kSessionStart;
}

std::string SerializeDiagnosticEventJsonl(const DiagnosticEvent& event) {
  std::ostringstream out;
  bool first = true;
  out << '{';
  AppendIntField(out, "schema_version", event.schema_version, first);
  AppendStringField(out, "event_type", ToString(event.type), first);
  AppendUintField(out, "sequence", event.sequence, first);
  AppendStringField(out, "wall_time_utc", event.wall_time_utc, first);
  AppendUintField(out, "qpc_ticks", event.qpc_ticks, first);
  AppendStringField(out, "backend", ToString(event.backend), first);
  AppendStringField(out, "device_id", event.device_id, first);
  AppendStringField(out, "message", event.message, first);

  if (event.lifecycle_from) {
    AppendStringField(out, "lifecycle_from", ToString(*event.lifecycle_from), first);
  }
  if (event.lifecycle_to) {
    AppendStringField(out, "lifecycle_to", ToString(*event.lifecycle_to), first);
  }
  if (event.format) {
    AppendStringField(out, "format_signal", ToString(event.format->signal_mode), first);
    AppendUintField(out, "sample_rate_hz", event.format->sample_rate_hz, first);
    AppendStringField(out, "sample_type", ToString(event.format->sample_type), first);
    AppendUintField(out, "channel_count", event.format->channels.channel_count, first);
  }
  if (event.realtime_stats) {
    AppendUintField(out, "stats_frames_generated",
                    event.realtime_stats->frames_generated, first);
    AppendUintField(out, "stats_frames_submitted",
                    event.realtime_stats->frames_submitted, first);
    AppendUintField(out, "stats_callback_count",
                    event.realtime_stats->callback_count, first);
    AppendUintField(out, "stats_underrun_count",
                    event.realtime_stats->underrun_count, first);
  }

  if (!first) {
    out << ',';
  }
  out << "\"backend_fields\":{";
  bool first_backend_field = true;
  for (const auto& [key, value] : event.backend_fields) {
    if (!first_backend_field) {
      out << ',';
    }
    first_backend_field = false;
    out << '"' << EscapeJson(key) << "\":\"" << EscapeJson(value) << '"';
  }
  out << "}}\n";
  return out.str();
}

std::optional<DiagnosticEvent> ParseDiagnosticEventJsonl(const std::string& line) {
  const auto schema_version = ExtractInt(line, "schema_version");
  const auto event_type = ExtractString(line, "event_type");
  const auto sequence = ExtractUint(line, "sequence");
  if (!schema_version || !event_type || !sequence) {
    return std::nullopt;
  }

  DiagnosticEvent event;
  event.schema_version = *schema_version;
  event.type = DiagnosticEventTypeFromString(*event_type);
  event.sequence = *sequence;
  event.wall_time_utc = ExtractString(line, "wall_time_utc").value_or("");
  event.qpc_ticks = ExtractUint(line, "qpc_ticks").value_or(0);
  event.backend = BackendApiFromString(ExtractString(line, "backend").value_or(""));
  event.device_id = ExtractString(line, "device_id").value_or("");
  event.message = ExtractString(line, "message").value_or("");

  if (const auto from = ExtractString(line, "lifecycle_from")) {
    event.lifecycle_from = LifecycleStateFromString(*from);
  }
  if (const auto to = ExtractString(line, "lifecycle_to")) {
    event.lifecycle_to = LifecycleStateFromString(*to);
  }
  if (const auto sample_rate = ExtractUint(line, "sample_rate_hz")) {
    FormatDescriptor format;
    format.sample_rate_hz = static_cast<std::uint32_t>(*sample_rate);
    format.channels.channel_count =
        static_cast<std::uint32_t>(ExtractUint(line, "channel_count").value_or(2));
    event.format = format;
  }
  if (const auto frames = ExtractUint(line, "stats_frames_generated")) {
    RealtimeStats stats;
    stats.frames_generated = *frames;
    stats.frames_submitted = ExtractUint(line, "stats_frames_submitted").value_or(0);
    stats.callback_count = ExtractUint(line, "stats_callback_count").value_or(0);
    stats.underrun_count = ExtractUint(line, "stats_underrun_count").value_or(0);
    event.realtime_stats = stats;
  }

  event.backend_fields = ExtractBackendFields(line);
  return event;
}

DiagnosticEvent MakeLifecycleEvent(
    std::uint64_t sequence,
    BackendApi backend,
    std::string device_id,
    DeviceLifecycleState from,
    DeviceLifecycleState to,
    std::string message) {
  DiagnosticEvent event;
  event.type = DiagnosticEventType::kLifecycleTransition;
  event.sequence = sequence;
  event.backend = backend;
  event.device_id = std::move(device_id);
  event.message = std::move(message);
  event.lifecycle_from = from;
  event.lifecycle_to = to;
  return event;
}

}  // namespace aptt
