#include <cstdlib>
#include <filesystem>
#include <fstream>
#include <iostream>
#include <string>
#include <string_view>

#include "core/diagnostic_planner.h"

namespace {

void Expect(bool condition, std::string_view message) {
  if (!condition) {
    std::cerr << "FAILED: " << message << "\n";
    std::exit(1);
  }
}

void TestNullDryRunIsAcceptedWithoutDeviceId() {
  aptt::DiagnosticPlanRequest request;
  request.backend = aptt::BackendApi::kNull;
  request.modes = {aptt::DiagnosticTestMode::kDryRun};

  const auto plan = aptt::BuildDiagnosticDryRunPlan(request);
  Expect(plan.accepted, "Null dry run is accepted");
  Expect(!plan.opens_audio_device, "dry run does not open audio device");
  Expect(plan.steps.size() == 1, "one dry-run step");
}

void TestRealDeviceRequiresExplicitId() {
  aptt::DiagnosticPlanRequest request;
  request.backend = aptt::BackendApi::kWasapiExclusive;
  request.modes = {aptt::DiagnosticTestMode::kSingleRun};

  const auto plan = aptt::BuildDiagnosticDryRunPlan(request);
  Expect(!plan.accepted, "real device without ID is rejected");
  Expect(!plan.rejection_reasons.empty(), "rejection reason exists");
}

void TestUnsafeStressIsRejected() {
  aptt::DiagnosticPlanRequest request;
  request.backend = aptt::BackendApi::kWasapiExclusive;
  request.device_id = "endpoint-123";
  request.known_problem_device = true;
  request.hold_mode = aptt::HoldMode::kImmediateRelease;
  request.modes = {aptt::DiagnosticTestMode::kStartStopStress};

  const auto plan = aptt::BuildDiagnosticDryRunPlan(request);
  Expect(!plan.accepted, "unsafe stress is rejected");
  Expect(plan.rejection_reasons.size() >= 3, "stress rejection reasons are detailed");
}

void TestExpertStressWithCooldownIsAcceptedAsDryRunPlan() {
  aptt::DiagnosticPlanRequest request;
  request.backend = aptt::BackendApi::kWasapiExclusive;
  request.device_id = "endpoint-123";
  request.known_problem_device = true;
  request.hold_mode = aptt::HoldMode::kKeepWarmAfterStop;
  request.cooldown_ms = 1500;
  request.expert_allow_stress = true;
  request.modes = {aptt::DiagnosticTestMode::kStartStopStress};

  const auto plan = aptt::BuildDiagnosticDryRunPlan(request);
  Expect(plan.accepted, "expert stress with cooldown is accepted as a plan");
  Expect(!plan.opens_audio_device, "accepted dry-run plan still opens no device");
  Expect(plan.steps.front().opens_audio_device == false, "step opens no device");
}

void TestReinitializeLoopNeedsLongCooldownForKnownProblemDevice() {
  aptt::DiagnosticPlanRequest request;
  request.backend = aptt::BackendApi::kAsio;
  request.device_id = "asio-driver";
  request.known_problem_device = true;
  request.expert_allow_reinitialize = true;
  request.cooldown_ms = 2000;
  request.max_reacquire_attempts = 2;
  request.modes = {aptt::DiagnosticTestMode::kReinitializeLoop};

  const auto rejected = aptt::BuildDiagnosticDryRunPlan(request);
  Expect(!rejected.accepted, "known-problem reinitialize loop needs long cooldown");

  request.cooldown_ms = 5000;
  const auto accepted = aptt::BuildDiagnosticDryRunPlan(request);
  Expect(accepted.accepted, "known-problem reinitialize loop accepts long cooldown");
}

void TestAutomaticSingleDeviceRequiresExplicitSelectedDevice() {
  aptt::DiagnosticPlanRequest request;
  request.backend = aptt::BackendApi::kDirectSound;
  request.modes = {aptt::DiagnosticTestMode::kSingleRun};
  request.cooldown_ms = 1500;

  const auto plan = aptt::BuildAutomaticSingleDevicePlan(request);
  Expect(!plan.accepted, "automatic real-device plan without selected ID is rejected");
  Expect(!plan.rejection_reasons.empty(),
         "automatic plan rejection explains missing selected ID");
}

void TestAutomaticSingleDevicePlanMarksAudioOpenAndBundleExport() {
  aptt::DiagnosticPlanRequest request;
  request.backend = aptt::BackendApi::kWasapiShared;
  request.device_id = "wasapi:{fixture-endpoint}";
  request.hold_mode = aptt::HoldMode::kKeepWarmAfterStop;
  request.cooldown_ms = 1500;
  request.max_reacquire_attempts = 1;
  request.modes = {aptt::DiagnosticTestMode::kSingleRun};

  const auto plan = aptt::BuildAutomaticSingleDevicePlan(request);
  Expect(plan.accepted, "automatic selected-device plan is accepted");
  Expect(plan.opens_audio_device,
         "automatic real-device execution plan marks that it opens audio");
  Expect(plan.bundle_export_required, "automatic plan requires bundle export");
  Expect(plan.steps.size() == 1, "one automatic single-run step");
  Expect(plan.steps.front().opens_audio_device,
         "single-run step marks audio device open");
  Expect(plan.steps.front().max_reacquire_attempts == 1,
         "single-run step carries bounded reacquire attempts");
}

void TestKnownProblemAutomaticPlanRejectsFullRelease() {
  aptt::DiagnosticPlanRequest request;
  request.backend = aptt::BackendApi::kAsio;
  request.device_id = "asio-driver";
  request.known_problem_device = true;
  request.hold_mode = aptt::HoldMode::kReleaseAfterDrain;
  request.cooldown_ms = 5000;
  request.max_reacquire_attempts = 1;
  request.modes = {aptt::DiagnosticTestMode::kSingleRun};

  const auto plan = aptt::BuildAutomaticSingleDevicePlan(request);
  Expect(!plan.accepted,
         "known-problem automatic plan rejects full release hold mode");
}

void TestAutomaticPlanSerializesPolicies() {
  aptt::DiagnosticPlanRequest request;
  request.backend = aptt::BackendApi::kWasapiExclusive;
  request.device_id = "endpoint-123";
  request.cooldown_ms = 1500;
  request.max_reacquire_attempts = 1;

  const auto plan = aptt::BuildAutomaticSingleDevicePlan(request);
  const auto json = aptt::SerializeDiagnosticPlanJson(plan);
  Expect(json.find("\"bundle_export_required\": true") != std::string::npos,
         "automatic plan JSON records bundle export requirement");
  Expect(json.find("stop-on-first-fault") != std::string::npos,
         "automatic plan JSON records abort policy");
  Expect(json.find("\"opens_audio_device\": true") != std::string::npos,
         "automatic plan JSON records real-device execution");
}

void TestAutomaticMatrixAndRampRequireCooldown() {
  aptt::DiagnosticPlanRequest request;
  request.backend = aptt::BackendApi::kWasapiShared;
  request.device_id = "endpoint-123";
  request.hold_mode = aptt::HoldMode::kKeepWarmAfterStop;
  request.max_reacquire_attempts = 1;
  request.modes = {
      aptt::DiagnosticTestMode::kFormatMatrix,
      aptt::DiagnosticTestMode::kBufferRampDown,
  };

  const auto rejected = aptt::BuildAutomaticSingleDevicePlan(request);
  Expect(!rejected.accepted,
         "automatic matrix/ramp plan without cooldown is rejected");

  request.cooldown_ms = 1500;
  const auto accepted = aptt::BuildAutomaticSingleDevicePlan(request);
  Expect(accepted.accepted,
         "automatic matrix/ramp plan accepts explicit cooldown");
  Expect(accepted.steps.size() == 2,
         "automatic matrix/ramp plan contains bounded planned steps");
  for (const auto& step : accepted.steps) {
    Expect(step.cooldown_before_ms == 1500,
           "matrix/ramp step carries cooldown");
    Expect(step.max_reacquire_attempts == 1,
           "matrix/ramp step keeps reacquire attempts bounded");
    Expect(step.abort_on_fault, "matrix/ramp step aborts on fault");
  }
}

void TestDiagnosticRunScorePenalizesFailures() {
  aptt::DiagnosticPlanRequest request;
  request.backend = aptt::BackendApi::kWasapiShared;
  request.device_id = "endpoint-123";
  request.cooldown_ms = 1500;
  const auto plan = aptt::BuildAutomaticSingleDevicePlan(request);

  aptt::RealtimeStats stats;
  stats.frames_submitted = 48000;
  stats.frames_accepted = 24000;
  stats.callback_count = 10;
  stats.underrun_count = 2;
  stats.device_invalidated_count = 1;

  const auto score = aptt::ScoreDiagnosticRun(plan, stats, false);
  Expect(score.score < 70, "failed run receives low score");
  Expect(score.rating == "fail", "failed run rating is fail");
  const auto json = aptt::SerializeDiagnosticRunScoreJson(score);
  Expect(json.find("device invalidation recorded") != std::string::npos,
         "score JSON records device invalidation penalty");
}

void TestFixturePlanMentionsNoDeviceOpen() {
#ifndef APTT_SOURCE_DIR
#error "APTT_SOURCE_DIR must be defined"
#endif
  const std::filesystem::path fixture_path =
      std::filesystem::path(APTT_SOURCE_DIR) / "tests" / "fixtures" /
      "diagnostic_plan.json";
  std::ifstream fixture(fixture_path);
  Expect(fixture.good(), "diagnostic plan fixture exists");
  const std::string content((std::istreambuf_iterator<char>(fixture)),
                            std::istreambuf_iterator<char>());
  Expect(content.find("\"opens_audio_device\": false") != std::string::npos,
         "fixture states no audio device open");
}

}  // namespace

int main() {
  TestNullDryRunIsAcceptedWithoutDeviceId();
  TestRealDeviceRequiresExplicitId();
  TestUnsafeStressIsRejected();
  TestExpertStressWithCooldownIsAcceptedAsDryRunPlan();
  TestReinitializeLoopNeedsLongCooldownForKnownProblemDevice();
  TestAutomaticSingleDeviceRequiresExplicitSelectedDevice();
  TestAutomaticSingleDevicePlanMarksAudioOpenAndBundleExport();
  TestKnownProblemAutomaticPlanRejectsFullRelease();
  TestAutomaticPlanSerializesPolicies();
  TestAutomaticMatrixAndRampRequireCooldown();
  TestDiagnosticRunScorePenalizesFailures();
  TestFixturePlanMentionsNoDeviceOpen();

  std::cout << "diagnostic planner tests passed\n";
  return 0;
}
