#include <cstdlib>
#include <iostream>
#include <string_view>

#include "backends/directsound_device_discovery.h"

namespace {

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

void TestDirectSoundStableIds() {
  Expect(aptt::DirectSoundDefaultStableId() == "directsound:default",
         "DirectSound default stable ID is explicit");
  Expect(aptt::DirectSoundGuidStableIdFromText(
             "{01234567-89AB-CDEF-0123-456789ABCDEF}") ==
             "directsound:{01234567-89AB-CDEF-0123-456789ABCDEF}",
         "DirectSound GUID stable ID preserves GUID text");
}

void TestDirectSoundCandidateDefaultsAreConservativePcm() {
  const auto candidates = aptt::DirectSoundDefaultPcmCandidates();
  Expect(candidates.size() == 65, "wide default DirectSound PCM candidates");
  Expect(candidates.front().signal_mode == aptt::SignalMode::kPcm,
         "default DirectSound candidate is PCM");
  Expect(candidates.front().sample_type == aptt::SampleType::kInt16,
         "default DirectSound candidate is int16");
  Expect(candidates[1].sample_type == aptt::SampleType::kInt24Packed,
         "default DirectSound candidates distinguish packed 24-bit");
  Expect(candidates[1].valid_bits_per_sample == 24 &&
             candidates[1].container_bits_per_sample == 24,
         "packed 24-bit DirectSound candidate is 24/24");
  Expect(candidates[2].sample_type == aptt::SampleType::kInt24In32,
         "default DirectSound candidates distinguish 24-in-32");
  Expect(candidates[2].valid_bits_per_sample == 24 &&
             candidates[2].container_bits_per_sample == 32,
         "24-in-32 DirectSound candidate is 24/32");
  Expect(candidates.front().channels.channel_count == 2,
         "default DirectSound candidate is stereo");
  Expect(aptt::DirectSoundProbeBufferBytes(candidates.front()) > 0,
         "probe buffer size is nonzero");
  Expect(candidates.back().sample_rate_hz == 1536000,
         "default DirectSound candidates include 1536 kHz");
  Expect(candidates.back().sample_type == aptt::SampleType::kInt32,
         "default DirectSound candidates include 32-bit integer");
}

void TestDirectSoundSnapshotRepresentsDefaultAndGuid() {
  const auto default_device = aptt::MakeDirectSoundDeviceSnapshot(
      aptt::DirectSoundDefaultStableId(),
      "Primary Sound Driver",
      "",
      true);
  Expect(default_device.device.backend == aptt::BackendApi::kDirectSound,
         "default snapshot uses DirectSound backend");
  Expect(default_device.device.is_default, "default device is marked default");
  Expect(default_device.backend_fields.at("id_kind") ==
             "directsound-default-device",
         "default ID kind is logged");
  Expect(default_device.probes.front().backend_fields.at("starts_stream") ==
             "false",
         "default discovery does not start streams");
  Expect(default_device.probes.front().backend_fields.at(
             "creates_secondary_buffer") == "false",
         "default discovery does not create secondary buffers");

  const auto guid_device = aptt::MakeDirectSoundDeviceSnapshot(
      aptt::DirectSoundGuidStableIdFromText(
          "{01234567-89AB-CDEF-0123-456789ABCDEF}"),
      "Fixture DirectSound Device",
      "fixture.dll",
      false);
  Expect(guid_device.device.stable_id ==
             "directsound:{01234567-89AB-CDEF-0123-456789ABCDEF}",
         "GUID stable ID is preserved");
  Expect(guid_device.backend_fields.at("device_guid") ==
             "{01234567-89AB-CDEF-0123-456789ABCDEF}",
         "GUID text is logged separately from stable ID");
  Expect(guid_device.backend_fields.at("driver_module") == "fixture.dll",
         "driver module is logged");
  Expect(guid_device.backend_fields.at("cooperative_level") == "deferred",
         "cooperative level is not changed by enumeration");
}

void TestDirectSoundFormatProbeRecordDoesNotPlay() {
  const auto candidate =
      aptt::DirectSoundPcmCandidate(44100, aptt::SampleType::kInt16, 2);
  const auto supported = aptt::MakeDirectSoundFormatProbeRecord(candidate, 0);

  Expect(supported.kind == aptt::CapabilityProbeKind::kFormatSupport,
         "format probe record kind");
  Expect(supported.support == aptt::CapabilitySupport::kSupported,
         "zero HRESULT is supported");
  Expect(supported.accepted_format.has_value(),
         "supported format is accepted");
  Expect(supported.backend_fields.at("api") ==
             "IDirectSound::CreateSoundBuffer",
         "CreateSoundBuffer probe API is logged");
  Expect(supported.backend_fields.at("creates_secondary_buffer") == "true",
         "secondary buffer creation is explicit");
  Expect(supported.backend_fields.at("plays_secondary_buffer") == "false",
         "secondary buffer playback is not used");
  Expect(supported.backend_fields.at("starts_stream") == "false",
         "format probe does not start stream");
  Expect(supported.backend_fields.at("lock_unlock") == "not-used",
         "lock/unlock remains out of scope");
  Expect(supported.backend_fields.at("restore") == "not-used",
         "restore remains out of scope");

  const auto failed = aptt::MakeDirectSoundFormatProbeRecord(candidate, 999);
  Expect(failed.support == aptt::CapabilitySupport::kFailed,
         "unknown HRESULT is failed");
  Expect(failed.error.has_value(), "failed HRESULT is logged");
}

void TestDirectSoundProbeRequiresExplicitDeviceBeforeAnyApiUse() {
  aptt::DirectSoundFormatProbeRequest request;
  request.candidate_formats.push_back(
      aptt::DirectSoundPcmCandidate(48000, aptt::SampleType::kInt16, 2));

  const auto result = aptt::ProbeDirectSoundDeviceFormats(request);

  Expect(result.device.stable_id.empty(), "no implicit DirectSound device is selected");
  Expect(result.discovery_error.has_value(),
         "missing DirectSound device returns error");
  Expect(result.discovery_error->symbol == "E_INVALIDARG",
         "missing DirectSound device is rejected before probing");
  Expect(result.probes.empty(),
         "no probe records are created without DirectSound device ID");
}

void TestDirectSoundSnapshotRoundtripsThroughCommonModel() {
  aptt::DiscoverySnapshot discovery;
  discovery.backend = aptt::BackendApi::kDirectSound;
  discovery.devices.push_back(aptt::MakeDirectSoundDeviceSnapshot(
      aptt::DirectSoundGuidStableIdFromText(
          "{01234567-89AB-CDEF-0123-456789ABCDEF}"),
      "Fixture DirectSound",
      "fixture.dll",
      false));
  discovery.devices.front().probes.push_back(
      aptt::MakeDirectSoundFormatProbeRecord(
          aptt::DirectSoundPcmCandidate(48000, aptt::SampleType::kInt16, 2),
          0));

  const auto parsed = aptt::ParseDiscoverySnapshotJson(
      aptt::SerializeDiscoverySnapshotJson(discovery));

  Expect(parsed.has_value(), "DirectSound discovery snapshot parses");
  Expect(aptt::DiscoverySnapshotIsSafeDryRun(*parsed),
         "top-level enumeration snapshot remains dry-run safe");
  const auto device = aptt::FindDeviceByStableId(
      *parsed,
      "directsound:{01234567-89AB-CDEF-0123-456789ABCDEF}");
  Expect(device.has_value(), "DirectSound device ID roundtrips");
  Expect(device->probes.size() == 2, "DirectSound probes roundtrip");
  Expect(device->probes.back().backend_fields.at("plays_secondary_buffer") ==
             "false",
         "no-playback field roundtrips");
}

}  // namespace

int main() {
  TestDirectSoundStableIds();
  TestDirectSoundCandidateDefaultsAreConservativePcm();
  TestDirectSoundSnapshotRepresentsDefaultAndGuid();
  TestDirectSoundFormatProbeRecordDoesNotPlay();
  TestDirectSoundProbeRequiresExplicitDeviceBeforeAnyApiUse();
  TestDirectSoundSnapshotRoundtripsThroughCommonModel();

  std::cout << "DirectSound device discovery tests passed\n";
  return 0;
}
