#include <cmath>
#include <cstdlib>
#include <cstring>
#include <filesystem>
#include <fstream>
#include <iostream>
#include <iterator>
#include <string>
#include <string_view>
#include <vector>

#include "backends/asio_pcm_backend.h"
#include "backends/asio_driver_discovery.h"
#include "core/dop_generator.h"
#include "core/pcm_diagnostics.h"

#ifndef APTT_ENABLE_ASIO
#define APTT_ENABLE_ASIO 0
#endif

namespace {

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

aptt::FormatDescriptor PcmFormat(aptt::SampleType sample_type,
                                 std::uint32_t valid_bits,
                                 std::uint32_t container_bits) {
  aptt::FormatDescriptor format;
  format.signal_mode = aptt::SignalMode::kPcm;
  format.sample_rate_hz = 48000;
  format.sample_type = sample_type;
  format.valid_bits_per_sample = valid_bits;
  format.container_bits_per_sample = container_bits;
  format.channels.channel_count = 2;
  format.channels.channel_mask = 3;
  return format;
}

aptt::FormatDescriptor DopFormat(std::uint32_t dsd_rate_multiplier = 64) {
  aptt::DopConfig dop;
  dop.dsd_rate_multiplier = dsd_rate_multiplier;
  dop.channel_count = 2;
  return aptt::MakeDopCarrierFormat(dop);
}

aptt::PlaybackConfig AsioPlaybackConfig() {
  aptt::PlaybackConfig config;
  config.backend = aptt::BackendApi::kAsio;
  config.device.backend = aptt::BackendApi::kAsio;
  config.device.display_name = "Fixture ASIO Driver";
  config.device.driver_name = "Fixture ASIO Driver";
  config.device.stable_id = "asio:{01234567-89AB-CDEF-0123-456789ABCDEF}";
  config.device.is_known_problem_device = true;
  config.format = PcmFormat(aptt::SampleType::kFloat32, 32, 32);
  config.hold_mode = aptt::HoldMode::kKeepWarmAfterStop;
  config.release_cooldown_ms = 1000;
  return config;
}

template <typename T>
T ReadScalar(const std::vector<std::uint8_t>& bytes) {
  T value{};
  std::memcpy(&value, bytes.data(), sizeof(T));
  return value;
}

std::string ReadTextFile(const std::filesystem::path& path) {
  std::ifstream file(path, std::ios::binary);
  return std::string((std::istreambuf_iterator<char>(file)),
                     std::istreambuf_iterator<char>());
}

void TestPolicyIsConservative() {
  auto config = AsioPlaybackConfig();
  const auto policy = aptt::MakeAsioPcmPolicy(config);

  Expect(policy.requires_explicit_driver,
         "ASIO PCM requires an explicit driver");
  Expect(policy.keep_warm_after_signal,
         "ASIO PCM defaults to keep-warm after signal");
  Expect(!policy.release_after_drain,
         "ASIO PCM does not default to release-after-drain");
  Expect(!policy.opens_control_panel,
         "ASIO PCM never opens the control panel automatically");
  Expect(!policy.unloads_driver_during_session,
         "ASIO PCM does not unload driver during session");
  Expect(!policy.uses_repeated_asio_exit,
         "ASIO PCM avoids repeated ASIOExit loops");
  Expect(policy.uses_output_ready_if_supported,
         "ASIO PCM can use ASIOOutputReady when supported");
}

void TestFormatGateAcceptsPcmOnly() {
  Expect(aptt::IsAsioPcmPlaybackFormat(
             PcmFormat(aptt::SampleType::kInt16, 16, 16)),
         "ASIO PCM accepts int16");
  Expect(aptt::IsAsioPcmPlaybackFormat(
             PcmFormat(aptt::SampleType::kInt24Packed, 24, 24)),
         "ASIO PCM accepts packed int24");
  Expect(aptt::IsAsioPcmPlaybackFormat(
             PcmFormat(aptt::SampleType::kInt24In32, 24, 32)),
         "ASIO PCM accepts int24-in-32");
  Expect(aptt::IsAsioPcmPlaybackFormat(
             PcmFormat(aptt::SampleType::kInt32, 32, 32)),
         "ASIO PCM accepts int32");
  Expect(aptt::IsAsioPcmPlaybackFormat(
             PcmFormat(aptt::SampleType::kFloat32, 32, 32)),
         "ASIO PCM accepts float32");
  Expect(aptt::IsAsioPcmPlaybackFormat(
             PcmFormat(aptt::SampleType::kFloat64, 64, 64)),
         "ASIO PCM accepts float64");

  auto dop = PcmFormat(aptt::SampleType::kInt24In32, 24, 32);
  dop.signal_mode = aptt::SignalMode::kDop;
  Expect(!aptt::IsAsioPcmPlaybackFormat(dop),
         "ASIO PCM rejects DoP carrier as PCM");

  auto dsd = PcmFormat(aptt::SampleType::kDsd1BitLsbFirst, 1, 8);
  dsd.signal_mode = aptt::SignalMode::kAsioNativeDsd;
  Expect(!aptt::IsAsioPcmPlaybackFormat(dsd),
         "ASIO PCM rejects native DSD");
}

void TestDopCarrierGateRequiresExactCarrier() {
  const auto dop = DopFormat();
  Expect(aptt::IsAsioDopCarrierFormat(dop),
         "ASIO DoP accepts exact 24-in-32 carrier");
  Expect(!aptt::IsAsioPcmPlaybackFormat(dop),
         "ASIO PCM gate still rejects DoP carriers");

  auto wrong_rate = dop;
  wrong_rate.sample_rate_hz = 192000;
  Expect(!aptt::IsAsioDopCarrierFormat(wrong_rate),
         "ASIO DoP rejects mismatched carrier rate");

  auto wrong_sample = dop;
  wrong_sample.sample_type = aptt::SampleType::kFloat32;
  wrong_sample.valid_bits_per_sample = 32;
  wrong_sample.container_bits_per_sample = 32;
  Expect(!aptt::IsAsioDopCarrierFormat(wrong_sample),
         "ASIO DoP rejects non-24-in-32 carriers");

  auto unsupported = DopFormat(4096);
  Expect(!aptt::IsAsioDopCarrierFormat(unsupported),
         "ASIO DoP rejects unsupported DSD multipliers");
}

void TestBytesPerSample() {
  Expect(aptt::AsioBytesPerSample(aptt::AsioPcmSampleType::kInt16Lsb) == 2,
         "int16 is two bytes");
  Expect(aptt::AsioBytesPerSample(aptt::AsioPcmSampleType::kInt24Lsb) == 3,
         "int24 is three bytes");
  Expect(aptt::AsioBytesPerSample(aptt::AsioPcmSampleType::kInt24In32Lsb) == 4,
         "int24-in-32 is four bytes");
  Expect(aptt::AsioBytesPerSample(aptt::AsioPcmSampleType::kFloat64Lsb) == 8,
         "float64 is eight bytes");
}

void TestPlanarInt16Conversion() {
  const std::vector<float> interleaved{1.0F, -1.0F, 0.0F, 0.5F};
  const auto planar = aptt::ConvertFloat32InterleavedToAsioPlanar(
      interleaved,
      2,
      aptt::AsioPcmSampleType::kInt16Lsb);

  Expect(planar.size() == 2, "int16 conversion creates two planes");
  Expect((planar[0] == std::vector<std::uint8_t>{0xFF, 0x7F, 0x00, 0x00}),
         "left int16 plane is little endian");
  Expect((planar[1] == std::vector<std::uint8_t>{0x00, 0x80, 0x00, 0x40}),
         "right int16 plane is little endian");
}

void TestPlanarInt24In32ConversionPadsLowByte() {
  const std::vector<float> interleaved{1.0F, 0.0F};
  const auto planar = aptt::ConvertFloat32InterleavedToAsioPlanar(
      interleaved,
      1,
      aptt::AsioPcmSampleType::kInt24In32Lsb);

  Expect(planar.size() == 1, "int24-in-32 conversion creates one plane");
  Expect(planar[0].size() == 8, "int24-in-32 writes four bytes per sample");
  Expect(planar[0][0] == 0x00, "int24-in-32 leaves low pad byte clear");
  Expect(planar[0][4] == 0x00 && planar[0][5] == 0x00 &&
             planar[0][6] == 0x00 && planar[0][7] == 0x00,
         "int24-in-32 silence is all zero bytes");
}

void TestPlanarFloat64Conversion() {
  const std::vector<float> interleaved{0.25F, -0.5F};
  const auto planar = aptt::ConvertFloat32InterleavedToAsioPlanar(
      interleaved,
      2,
      aptt::AsioPcmSampleType::kFloat64Lsb);

  Expect(planar.size() == 2, "float64 conversion creates two planes");
  Expect(std::fabs(ReadScalar<double>(planar[0]) - 0.25) < 0.000001,
         "left float64 value is preserved");
  Expect(std::fabs(ReadScalar<double>(planar[1]) + 0.5) < 0.000001,
         "right float64 value is preserved");
}

void TestInvalidInterleavedInputFailsClosed() {
  const auto planar = aptt::ConvertFloat32InterleavedToAsioPlanar(
      {0.0F, 1.0F, 2.0F},
      2,
      aptt::AsioPcmSampleType::kFloat32Lsb);
  Expect(planar.empty(), "invalid interleaved frame count fails closed");
}

void TestDopCarrierPlanarConversionPreservesMarkers() {
  const std::vector<std::uint8_t> interleaved = {
      0x10, 0x11, 0x05, 0x00,
      0x20, 0x21, 0x05, 0x00,
      0x12, 0x13, 0xFA, 0x00,
      0x22, 0x23, 0xFA, 0x00,
  };

  const auto planar =
      aptt::ConvertInterleavedDop24In32ToAsioPlanar(interleaved, 2);
  Expect(planar.size() == 2, "DoP conversion creates two ASIO planes");
  Expect((planar[0] == std::vector<std::uint8_t>{
              0x10, 0x11, 0x05, 0x00,
              0x12, 0x13, 0xFA, 0x00}),
         "left DoP plane preserves frame markers");
  Expect((planar[1] == std::vector<std::uint8_t>{
              0x20, 0x21, 0x05, 0x00,
              0x22, 0x23, 0xFA, 0x00}),
         "right DoP plane preserves frame markers");

  const auto invalid =
      aptt::ConvertInterleavedDop24In32ToAsioPlanar({0x00, 0x01, 0x05}, 2);
  Expect(invalid.empty(), "invalid DoP carrier byte count fails closed");
}

void TestPreparedEventLogsBufferAndLatency() {
  aptt::AsioBufferConstraints constraints;
  constraints.min_size = 64;
  constraints.max_size = 2048;
  constraints.preferred_size = 256;
  constraints.granularity = -1;
  constraints.input_latency = 128;
  constraints.output_latency = 256;
  constraints.output_ready_supported = true;

  const auto event = aptt::MakeAsioPcmPreparedEvent(
      7,
      "asio:{fixture}",
      PcmFormat(aptt::SampleType::kFloat32, 32, 32),
      constraints);

  Expect(event.backend == aptt::BackendApi::kAsio,
         "prepared event records ASIO backend");
  Expect(event.backend_fields.at("buffer_min") == "64",
         "prepared event logs min buffer");
  Expect(event.backend_fields.at("buffer_max") == "2048",
         "prepared event logs max buffer");
  Expect(event.backend_fields.at("buffer_preferred") == "256",
         "prepared event logs preferred buffer");
  Expect(event.backend_fields.at("buffer_granularity") == "-1",
         "prepared event logs granularity");
  Expect(event.backend_fields.at("input_latency") == "128",
         "prepared event logs input latency");
  Expect(event.backend_fields.at("output_latency") == "256",
         "prepared event logs output latency");
  Expect(event.backend_fields.at("output_ready_supported") == "true",
         "prepared event logs output-ready support");
  Expect(event.backend_fields.at("keep_alive_format") == "valid-pcm-silence",
         "prepared event logs valid PCM silence keep-warm");
  Expect(event.backend_fields.at("repeated_asio_exit") == "false",
         "prepared event logs ASIOExit loop guard");
}

void TestPreparedEventLogsDopCarrierFields() {
  aptt::AsioBufferConstraints constraints;
  constraints.min_size = 64;
  constraints.max_size = 2048;
  constraints.preferred_size = 256;
  constraints.output_latency = 256;

  const auto event = aptt::MakeAsioPcmPreparedEvent(
      9,
      "asio:{fixture}",
      DopFormat(),
      constraints);

  Expect(event.message.find("DoP") != std::string::npos,
         "prepared event names ASIO DoP carrier");
  Expect(event.backend_fields.at("signal_mode") == "DoP",
         "prepared event logs DoP signal mode");
  Expect(event.backend_fields.at("keep_alive_format") == "valid-dop-silence",
         "prepared event logs valid DoP silence keep-warm");
  Expect(event.backend_fields.at("dop_dsd_rate_multiplier") == "64",
         "prepared event logs DoP DSD rate");
  Expect(event.backend_fields.at("dop_marker_first") == "0x05",
         "prepared event logs first marker");
  Expect(event.backend_fields.at("pcm_carrier_sample_type") == "int24-in-32",
         "prepared event logs PCM carrier sample type");
}

void TestRuntimeEventLogsCallbackTiming() {
  aptt::RealtimeStats stats;
  stats.callback_count = 3;
  stats.frames_submitted = 768;
  stats.frames_accepted = 768;

  const auto event = aptt::MakeAsioPcmRuntimeEvent(
      8,
      "asio:{fixture}",
      stats,
      1,
      256,
      0.25,
      true,
      true);

  Expect(event.type == aptt::DiagnosticEventType::kRuntimeStats,
         "runtime event is runtime stats");
  Expect(event.realtime_stats.has_value(),
         "runtime event preserves stats");
  Expect(event.backend_fields.at("buffer_index") == "1",
         "runtime event logs buffer index");
  Expect(event.backend_fields.at("buffer_frames") == "256",
         "runtime event logs buffer frames");
  Expect(event.backend_fields.at("direct_process") == "true",
         "runtime event logs directProcess flag");
  Expect(event.backend_fields.at("output_ready_called") == "true",
         "runtime event logs output-ready call");
}

void TestPcmSummaryKeepsAsioFields() {
  aptt::AsioBufferConstraints constraints;
  constraints.min_size = 32;
  constraints.max_size = 1024;
  constraints.preferred_size = 128;
  constraints.granularity = 0;
  constraints.input_latency = 16;
  constraints.output_latency = 32;
  constraints.output_ready_supported = false;

  std::vector<aptt::DiagnosticEvent> events;
  events.push_back(aptt::MakeAsioPcmPreparedEvent(
      1,
      "asio:{fixture}",
      PcmFormat(aptt::SampleType::kFloat32, 32, 32),
      constraints));
  aptt::RealtimeStats stats;
  stats.callback_count = 1;
  events.push_back(aptt::MakeAsioPcmRuntimeEvent(
      2,
      "asio:{fixture}",
      stats,
      0,
      128,
      0.5,
      false,
      false));

  const auto summary = aptt::BuildPcmRunSummary(
      aptt::BackendApi::kAsio,
      "asio:{fixture}",
      events,
      stats);

  Expect(summary.backend_fields.at("buffer_min") == "32",
         "summary preserves ASIO min buffer");
  Expect(summary.backend_fields.at("buffer_preferred") == "128",
         "summary preserves ASIO preferred buffer");
  Expect(summary.backend_fields.at("output_latency") == "32",
         "summary preserves ASIO latency");
  Expect(summary.backend_fields.at("callback_duration_ms").find("0.500") == 0,
         "summary preserves callback duration");
  Expect(summary.backend_fields.at("output_ready_called") == "false",
         "summary preserves output-ready callback flag");
}

void TestAsioReleaseFlagsAreClearedOnlyAfterSuccessfulCalls() {
#if defined(APTT_SOURCE_DIR)
  const auto source_root = std::filesystem::path(APTT_SOURCE_DIR);
  const std::vector<std::filesystem::path> sources = {
      "src/backends/asio_pcm_backend.cpp",
      "src/backends/asio_native_dsd_backend.cpp",
  };
  for (const auto& relative_path : sources) {
    const auto source = ReadTextFile(source_root / relative_path);
    const auto dispose_call = source.find("ASIODisposeBuffers()");
    const auto dispose_check = source.find("if (dispose_result != ASE_OK)",
                                          dispose_call);
    const auto buffers_clear = source.find("buffers_created_ = false",
                                           dispose_call);
    Expect(dispose_call != std::string::npos &&
               dispose_check != std::string::npos &&
               buffers_clear != std::string::npos &&
               dispose_check < buffers_clear,
           "ASIO buffers_created_ is cleared only after dispose success check");

    const auto exit_call = source.find("ASIOExit()");
    const auto exit_check = source.find("if (exit_result != ASE_OK)",
                                       exit_call);
    const auto driver_clear = source.find("driver_loaded_ = false",
                                          exit_call);
    Expect(exit_call != std::string::npos &&
               exit_check != std::string::npos &&
               driver_clear != std::string::npos &&
               exit_check < driver_clear,
           "ASIO driver_loaded_ is cleared only after exit success check");
  }
#endif
}

void TestDisabledBuildFailsWithoutDriverCalls() {
#if !APTT_ENABLE_ASIO
  aptt::AsioPcmBackend backend;
  const auto result = backend.initialize(AsioPlaybackConfig());

  Expect(!result.ok, "ASIO PCM initialize fails when build gate is OFF");
  Expect(result.error.message.find("ENABLE_ASIO is OFF") != std::string::npos,
         "disabled ASIO failure explains build gate");
  Expect(backend.lifecycle_state() == aptt::DeviceLifecycleState::kDiscovered,
         "disabled ASIO initialize does not reserve a driver");
  Expect(backend.diagnostic_events().empty(),
         "disabled ASIO initialize does not emit driver lifecycle events");

  const auto probe = backend.probeFormats(AsioPlaybackConfig().device);
  Expect(probe.supported_formats.empty(),
         "disabled ASIO probe returns no supported formats");
  Expect(!probe.rejection_reasons.empty(),
         "disabled ASIO probe explains rejection");

  const auto dop_probe = backend.probeDopFormats(AsioPlaybackConfig().device);
  Expect(dop_probe.supported_formats.empty(),
         "disabled ASIO DoP probe returns no supported formats");
  Expect(!dop_probe.rejection_reasons.empty(),
         "disabled ASIO DoP probe explains rejection");

  auto dop_config = AsioPlaybackConfig();
  dop_config.format = DopFormat();
  const auto dop_result = backend.initialize(dop_config);
  Expect(!dop_result.ok, "ASIO DoP initialize fails when build gate is OFF");
  Expect(dop_result.error.message.find("ENABLE_ASIO is OFF") != std::string::npos,
         "disabled ASIO DoP failure explains build gate");
#endif
}

void TestResetIsDisabled() {
  aptt::AsioPcmBackend backend;
  const auto result = backend.reset();

  Expect(!result.ok, "ASIO reset is disabled");
  Expect(result.error.message.find("reset is disabled") != std::string::npos,
         "ASIO reset failure explains safe lifecycle path");
}

}  // namespace

int main() {
  TestPolicyIsConservative();
  TestFormatGateAcceptsPcmOnly();
  TestDopCarrierGateRequiresExactCarrier();
  TestBytesPerSample();
  TestPlanarInt16Conversion();
  TestPlanarInt24In32ConversionPadsLowByte();
  TestPlanarFloat64Conversion();
  TestInvalidInterleavedInputFailsClosed();
  TestDopCarrierPlanarConversionPreservesMarkers();
  TestPreparedEventLogsBufferAndLatency();
  TestPreparedEventLogsDopCarrierFields();
  TestRuntimeEventLogsCallbackTiming();
  TestPcmSummaryKeepsAsioFields();
  TestAsioReleaseFlagsAreClearedOnlyAfterSuccessfulCalls();
  TestDisabledBuildFailsWithoutDriverCalls();
  TestResetIsDisabled();

  std::cout << "ASIO PCM backend tests passed\n";
  return 0;
}
