#include "core/diagnostic_planner.h"

#include <algorithm>
#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;
}

bool IsRealDeviceBackend(BackendApi backend) {
  return backend != BackendApi::kNull;
}

bool IsFullReleaseMode(HoldMode mode) {
  return mode == HoldMode::kReleaseAfterDrain ||
         mode == HoldMode::kImmediateRelease;
}

std::vector<DiagnosticTestMode> EffectiveModes(
    const DiagnosticPlanRequest& request,
    DiagnosticTestMode fallback) {
  return request.modes.empty() ? std::vector<DiagnosticTestMode>{fallback}
                               : request.modes;
}

std::vector<FormatDescriptor> EffectiveFormats(
    const DiagnosticPlanRequest& request) {
  return request.formats.empty() ? std::vector<FormatDescriptor>{FormatDescriptor{}}
                                 : request.formats;
}

void ValidateRiskyModes(const DiagnosticPlanRequest& request,
                        const std::vector<DiagnosticTestMode>& modes,
                        std::vector<std::string>& rejection_reasons) {
  for (const DiagnosticTestMode mode : modes) {
    if (mode == DiagnosticTestMode::kStartStopStress) {
      if (!request.expert_allow_stress) {
        rejection_reasons.push_back(
            "start/stop stress requires explicit expert stress permission");
      }
      if (request.cooldown_ms < 1000) {
        rejection_reasons.push_back(
            "start/stop stress requires cooldown_ms >= 1000");
      }
      if (request.known_problem_device &&
          request.hold_mode == HoldMode::kImmediateRelease) {
        rejection_reasons.push_back(
            "known-problem devices must not use immediate release for stress tests");
      }
    }

    if (mode == DiagnosticTestMode::kReinitializeLoop) {
      if (!request.expert_allow_reinitialize) {
        rejection_reasons.push_back(
            "reinitialize loop requires explicit expert reinitialize permission");
      }
      const std::uint32_t required_cooldown =
          request.known_problem_device ? 5000U : 2000U;
      if (request.cooldown_ms < required_cooldown) {
        rejection_reasons.push_back(
            "reinitialize loop cooldown is too short for the selected device risk");
      }
      if (request.max_reacquire_attempts > 3) {
        rejection_reasons.push_back(
            "reinitialize loop must keep max_reacquire_attempts <= 3");
      }
    }

    if ((mode == DiagnosticTestMode::kFormatMatrix ||
         mode == DiagnosticTestMode::kBufferRampDown) &&
        IsRealDeviceBackend(request.backend) && request.cooldown_ms < 1000) {
      rejection_reasons.push_back(
          "matrix and buffer-ramp diagnostics require cooldown_ms >= 1000");
    }
  }
}

}  // namespace

const char* ToString(DiagnosticTestMode mode) {
  switch (mode) {
    case DiagnosticTestMode::kDryRun:
      return "dry-run";
    case DiagnosticTestMode::kNullBackendRun:
      return "null-backend-run";
    case DiagnosticTestMode::kSingleRun:
      return "single-run";
    case DiagnosticTestMode::kFormatMatrix:
      return "format-matrix";
    case DiagnosticTestMode::kBufferRampDown:
      return "buffer-ramp-down";
    case DiagnosticTestMode::kStartStopStress:
      return "start-stop-stress";
    case DiagnosticTestMode::kReinitializeLoop:
      return "reinitialize-loop";
  }
  return "unknown";
}

DiagnosticPlan BuildDiagnosticDryRunPlan(const DiagnosticPlanRequest& request) {
  DiagnosticPlan plan;
  plan.opens_audio_device = false;
  plan.bundle_export_required = false;
  plan.cooldown_ms = request.cooldown_ms;
  plan.max_reacquire_attempts = request.max_reacquire_attempts;
  plan.abort_policy = "dry-run-only";
  plan.fault_policy = "report-plan-rejection-without-touching-device";
  plan.summary_scoring = "not-applicable";

  if (IsRealDeviceBackend(request.backend) && request.device_id.empty()) {
    plan.rejection_reasons.push_back(
        "real-device diagnostics require an explicit selected device ID");
  }

  const auto modes = EffectiveModes(request, DiagnosticTestMode::kDryRun);
  ValidateRiskyModes(request, modes, plan.rejection_reasons);

  if (!plan.rejection_reasons.empty()) {
    plan.accepted = false;
    return plan;
  }

  const auto formats = EffectiveFormats(request);
  for (const DiagnosticTestMode mode : modes) {
    for (const auto& format : formats) {
      plan.steps.push_back(DiagnosticPlanStep{
          .mode = mode,
          .backend = request.backend,
          .device_id = request.device_id,
          .format = format,
          .opens_audio_device = false,
          .cooldown_before_ms = 0,
          .max_reacquire_attempts = 0,
          .abort_on_fault = true,
          .export_bundle_after_step = false,
      });
    }
  }
  plan.accepted = true;
  return plan;
}

DiagnosticPlan BuildAutomaticSingleDevicePlan(
    const DiagnosticPlanRequest& request) {
  DiagnosticPlan plan;
  plan.bundle_export_required = true;
  plan.cooldown_ms = request.cooldown_ms;
  plan.max_reacquire_attempts = request.max_reacquire_attempts;
  plan.abort_policy = "stop-on-first-fault";
  plan.fault_policy =
      "record-fault-export-bundle-and-do-not-run-unbounded-retries";
  plan.summary_scoring =
      "100 minus penalties for failed start, underrun, discontinuity, "
      "device invalidation, and incomplete frame acceptance";

  if (IsRealDeviceBackend(request.backend) && request.device_id.empty()) {
    plan.rejection_reasons.push_back(
        "automatic real-device diagnostics require one explicit selected device ID");
  }
  if (request.known_problem_device && IsFullReleaseMode(request.hold_mode)) {
    plan.rejection_reasons.push_back(
        "known-problem automatic diagnostics require keep-warm or hold-initialized mode");
  }
  if (request.max_reacquire_attempts > 3) {
    plan.rejection_reasons.push_back(
        "automatic diagnostics must keep max_reacquire_attempts <= 3");
  }

  const auto modes = EffectiveModes(request, DiagnosticTestMode::kSingleRun);
  ValidateRiskyModes(request, modes, plan.rejection_reasons);

  if (!plan.rejection_reasons.empty()) {
    plan.accepted = false;
    return plan;
  }

  const auto formats = EffectiveFormats(request);
  for (const DiagnosticTestMode mode : modes) {
    for (const auto& format : formats) {
      const bool opens_audio =
          IsRealDeviceBackend(request.backend) &&
          mode != DiagnosticTestMode::kDryRun;
      plan.steps.push_back(DiagnosticPlanStep{
          .mode = mode,
          .backend = request.backend,
          .device_id = request.device_id,
          .format = format,
          .opens_audio_device = opens_audio,
          .cooldown_before_ms =
              mode == DiagnosticTestMode::kSingleRun ? 0U : request.cooldown_ms,
          .max_reacquire_attempts = request.max_reacquire_attempts,
          .abort_on_fault = true,
          .export_bundle_after_step = true,
      });
      plan.opens_audio_device = plan.opens_audio_device || opens_audio;
    }
  }

  plan.accepted = true;
  return plan;
}

DiagnosticRunScore ScoreDiagnosticRun(const DiagnosticPlan& plan,
                                      const RealtimeStats& stats,
                                      bool run_ok) {
  DiagnosticRunScore score;
  score.score = 100;
  if (!plan.accepted) {
    score.score -= 50;
    score.penalties.push_back("plan rejected");
  }
  if (!run_ok) {
    score.score -= 50;
    score.penalties.push_back("run failed");
  }
  if (stats.callback_count == 0 && stats.frames_submitted > 0) {
    score.score -= 10;
    score.penalties.push_back("no callbacks recorded");
  }
  if (stats.frames_submitted > stats.frames_accepted) {
    score.score -= 15;
    score.penalties.push_back("submitted frames were not fully accepted");
  }
  if (stats.underrun_count > 0) {
    score.score -= static_cast<int>(std::min<std::uint64_t>(
        stats.underrun_count * 10U,
        40U));
    score.penalties.push_back("underrun indicators recorded");
  }
  if (stats.discontinuity_count > 0) {
    score.score -= static_cast<int>(std::min<std::uint64_t>(
        stats.discontinuity_count * 5U,
        20U));
    score.penalties.push_back("discontinuities recorded");
  }
  if (stats.device_invalidated_count > 0) {
    score.score -= 40;
    score.penalties.push_back("device invalidation recorded");
  }
  score.score = std::clamp(score.score, 0, 100);
  if (score.score >= 90) {
    score.rating = "pass";
  } else if (score.score >= 70) {
    score.rating = "warning";
  } else {
    score.rating = "fail";
  }
  return score;
}

std::string SerializeDiagnosticPlanJson(const DiagnosticPlan& plan) {
  std::ostringstream out;
  out << "{\n";
  out << "  \"schema_version\": 1,\n";
  out << "  \"accepted\": " << (plan.accepted ? "true" : "false") << ",\n";
  out << "  \"opens_audio_device\": "
      << (plan.opens_audio_device ? "true" : "false") << ",\n";
  out << "  \"bundle_export_required\": "
      << (plan.bundle_export_required ? "true" : "false") << ",\n";
  out << "  \"cooldown_ms\": " << plan.cooldown_ms << ",\n";
  out << "  \"max_reacquire_attempts\": "
      << plan.max_reacquire_attempts << ",\n";
  out << "  \"abort_policy\": \"" << EscapeJson(plan.abort_policy) << "\",\n";
  out << "  \"fault_policy\": \"" << EscapeJson(plan.fault_policy) << "\",\n";
  out << "  \"summary_scoring\": \""
      << EscapeJson(plan.summary_scoring) << "\",\n";
  out << "  \"rejection_reasons\": [";
  for (std::size_t i = 0; i < plan.rejection_reasons.size(); ++i) {
    if (i != 0) {
      out << ", ";
    }
    out << '"' << EscapeJson(plan.rejection_reasons[i]) << '"';
  }
  out << "],\n";
  out << "  \"steps\": [\n";
  for (std::size_t i = 0; i < plan.steps.size(); ++i) {
    const auto& step = plan.steps[i];
    out << "    {\"mode\": \"" << ToString(step.mode) << "\", "
        << "\"backend\": \"" << ToString(step.backend) << "\", "
        << "\"device_id\": \"" << EscapeJson(step.device_id) << "\", "
        << "\"sample_rate_hz\": " << step.format.sample_rate_hz << ", "
        << "\"opens_audio_device\": "
        << (step.opens_audio_device ? "true" : "false") << ", "
        << "\"cooldown_before_ms\": " << step.cooldown_before_ms << ", "
        << "\"max_reacquire_attempts\": "
        << step.max_reacquire_attempts << ", "
        << "\"abort_on_fault\": "
        << (step.abort_on_fault ? "true" : "false") << ", "
        << "\"export_bundle_after_step\": "
        << (step.export_bundle_after_step ? "true" : "false") << "}";
    if (i + 1 != plan.steps.size()) {
      out << ',';
    }
    out << '\n';
  }
  out << "  ]\n";
  out << "}\n";
  return out.str();
}

std::string SerializeDiagnosticRunScoreJson(const DiagnosticRunScore& score) {
  std::ostringstream out;
  out << "{\n";
  out << "  \"schema_version\": 1,\n";
  out << "  \"score\": " << score.score << ",\n";
  out << "  \"rating\": \"" << EscapeJson(score.rating) << "\",\n";
  out << "  \"penalties\": [";
  for (std::size_t i = 0; i < score.penalties.size(); ++i) {
    if (i != 0) {
      out << ", ";
    }
    out << '"' << EscapeJson(score.penalties[i]) << '"';
  }
  out << "]\n";
  out << "}\n";
  return out.str();
}

}  // namespace aptt
