#include "../common.h"

namespace esphome::mitsubishi_cn105::testing {

struct TestContext {
  MockUARTComponent uart;
  uart::UARTDevice device{&uart};
  TestableMitsubishiCN105 sut{device};

  TestContext() { this->sut.set_current_time(0); }
};

TEST(MitsubishiCN105Tests, InitSendsConnectPacket) {
  auto ctx = TestContext{};

  ctx.sut.set_current_time(123);
  EXPECT_EQ(ctx.sut.state_, TestableMitsubishiCN105::State::NOT_CONNECTED);
  EXPECT_TRUE(ctx.uart.tx.empty());
  EXPECT_EQ(ctx.sut.operation_start_ms_, 0);

  ctx.sut.initialize();

  EXPECT_EQ(ctx.sut.state_, TestableMitsubishiCN105::State::CONNECTING);
  EXPECT_THAT(ctx.uart.tx, ::testing::ElementsAre(0xFC, 0x5A, 0x01, 0x30, 0x02, 0xCA, 0x01, 0xA8));
  EXPECT_EQ(ctx.sut.operation_start_ms_, 123);
}

TEST(MitsubishiCN105Tests, ConnectAndUpdateStatus) {
  auto ctx = TestContext{};

  ctx.sut.initialize();
  ctx.uart.tx.clear();  // Remove first connect packet bytes

  EXPECT_EQ(ctx.sut.state_, TestableMitsubishiCN105::State::CONNECTING);
  EXPECT_EQ(ctx.sut.operation_start_ms_, 0);

  // Connect response
  ctx.uart.push_rx({0xFC, 0x7A, 0x01, 0x30, 0x00, 0x55});

  ctx.sut.set_current_time(200);
  ASSERT_FALSE(ctx.sut.update());

  // All bytes from UART should be consumed
  EXPECT_TRUE(ctx.uart.rx.empty());
  // After successful connect we request status, first settings (0x02)
  EXPECT_EQ(ctx.sut.state_, TestableMitsubishiCN105::State::UPDATING_STATUS);
  EXPECT_THAT(ctx.uart.tx, ::testing::ElementsAre(0xFC, 0x42, 0x01, 0x30, 0x10, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00,
                                                  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7B));
  EXPECT_EQ(ctx.sut.operation_start_ms_, 200);

  // Clear TX bytes.
  ctx.uart.tx.clear();

  // Settings response
  ctx.uart.push_rx({0xFC, 0x62, 0x01, 0x30, 0x10, 0x02, 0x00, 0x00, 0x00, 0x08, 0x07,
                    0x00, 0x04, 0x00, 0x00, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3C});

  // Settings should still have initial values
  EXPECT_FALSE(ctx.sut.status().power_on);
  EXPECT_THAT(ctx.sut.status().target_temperature, ::testing::IsNan());
  EXPECT_EQ(ctx.sut.status().mode, MitsubishiCN105::Mode::UNKNOWN);
  EXPECT_EQ(ctx.sut.status().fan_mode, MitsubishiCN105::FanMode::UNKNOWN);
  EXPECT_EQ(ctx.sut.status().vane_mode, MitsubishiCN105::VaneMode::UNKNOWN);
  EXPECT_EQ(ctx.sut.status().wide_vane_mode, MitsubishiCN105::WideVaneMode::UNKNOWN);

  ctx.sut.set_current_time(300);
  ASSERT_FALSE(ctx.sut.update());
  EXPECT_TRUE(ctx.uart.rx.empty());

  // Check settings that we just read from received package
  EXPECT_FALSE(ctx.sut.status().power_on);
  EXPECT_EQ(ctx.sut.status().target_temperature, 24.0f);
  EXPECT_EQ(ctx.sut.status().mode, MitsubishiCN105::Mode::AUTO);
  EXPECT_EQ(ctx.sut.status().fan_mode, MitsubishiCN105::FanMode::AUTO);
  EXPECT_EQ(ctx.sut.status().vane_mode, MitsubishiCN105::VaneMode::POSITION_4);
  EXPECT_EQ(ctx.sut.status().wide_vane_mode, MitsubishiCN105::WideVaneMode::SWING);

  // Now fetch room temperature (0x03)
  EXPECT_EQ(ctx.sut.state_, TestableMitsubishiCN105::State::UPDATING_STATUS);
  EXPECT_THAT(ctx.uart.tx, ::testing::ElementsAre(0xFC, 0x42, 0x01, 0x30, 0x10, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00,
                                                  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7A));
  EXPECT_EQ(ctx.sut.operation_start_ms_, 300);

  // Clear TX bytes.
  ctx.uart.tx.clear();

  // Room temperature response
  ctx.uart.push_rx({0xFC, 0x62, 0x01, 0x30, 0x10, 0x03, 0x00, 0x00, 0x0B, 0x00, 0x00,
                    0xAA, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xA5});

  // Room temperature should still have initial value
  EXPECT_THAT(ctx.sut.status().room_temperature, ::testing::IsNan());

  ctx.sut.set_current_time(400);
  EXPECT_FALSE(ctx.sut.is_status_initialized());
  ASSERT_TRUE(ctx.sut.update());
  EXPECT_TRUE(ctx.uart.rx.empty());
  EXPECT_TRUE(ctx.sut.is_status_initialized());

  // Check room temperature we just read from received package
  EXPECT_EQ(ctx.sut.status().room_temperature, 21.0f);

  EXPECT_TRUE(ctx.uart.tx.empty());
  EXPECT_EQ(ctx.sut.state_, TestableMitsubishiCN105::State::WAITING_FOR_SCHEDULED_STATUS_UPDATE);
  EXPECT_EQ(ctx.sut.operation_start_ms_, 400);
}

TEST(MitsubishiCN105Tests, NoResponseTriggersReconnect) {
  auto ctx = TestContext{};

  ctx.sut.initialize();
  ctx.uart.tx.clear();  // Remove first connect packet bytes

  // No response (no RX data), no retry yet
  ASSERT_FALSE(ctx.sut.update());
  EXPECT_EQ(ctx.sut.state_, TestableMitsubishiCN105::State::CONNECTING);
  EXPECT_TRUE(ctx.uart.tx.empty());
  EXPECT_EQ(ctx.sut.operation_start_ms_, 0);

  // Still no response after 1999ms, no retry yet
  ctx.sut.set_current_time(1999);
  ASSERT_FALSE(ctx.sut.update());
  EXPECT_EQ(ctx.sut.state_, TestableMitsubishiCN105::State::CONNECTING);
  EXPECT_TRUE(ctx.uart.tx.empty());
  EXPECT_EQ(ctx.sut.operation_start_ms_, 0);

  // Stop waiting after 2s and retry connect
  ctx.sut.set_current_time(2000);
  ASSERT_FALSE(ctx.sut.update());
  EXPECT_EQ(ctx.sut.state_, TestableMitsubishiCN105::State::CONNECTING);
  EXPECT_THAT(ctx.uart.tx, ::testing::ElementsAre(0xFC, 0x5A, 0x01, 0x30, 0x02, 0xCA, 0x01, 0xA8));
  EXPECT_EQ(ctx.sut.operation_start_ms_, 2000);
}

TEST(MitsubishiCN105Tests, RxWatchdogLimitsProcessingPerUpdate) {
  auto ctx = TestContext{};

  ctx.sut.initialize();
  ctx.uart.tx.clear();  // Remove first connect packet bytes

  // RX noise/unexpected traffic
  ctx.uart.push_rx({0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E,
                    0x0F, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1A, 0x1B, 0x1C,
                    0x1D, 0x1E, 0x1F, 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2A,
                    0x2B, 0x2C, 0x2D, 0x2E, 0x2F, 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38,
                    0x39, 0x3A, 0x3B, 0x3C, 0x3D, 0x3E, 0x3F, 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46});

  // Make sure we have enough bytes in buffer.
  ASSERT_GT(ctx.uart.rx.size(), 64);

  // No valid response, no state change expected
  ASSERT_FALSE(ctx.sut.update());
  EXPECT_EQ(ctx.sut.state_, TestableMitsubishiCN105::State::CONNECTING);
  EXPECT_TRUE(ctx.uart.tx.empty());

  // Watchdog interrupts reading (max. 64 bytes at once) so we do not spend the whole loop draining UART
  EXPECT_FALSE(ctx.uart.rx.empty());

  // Next update will read remaining bytes, no state change expected
  ASSERT_FALSE(ctx.sut.update());
  EXPECT_EQ(ctx.sut.state_, TestableMitsubishiCN105::State::CONNECTING);
  EXPECT_TRUE(ctx.uart.tx.empty());
  EXPECT_TRUE(ctx.uart.rx.empty());
}

TEST(MitsubishiCN105Tests, ParserHandlesMixedRxStream) {
  auto ctx = TestContext{};

  ctx.sut.initialize();
  ctx.uart.tx.clear();  // Remove first connect packet bytes

  // Mixed RX stream with partial, malformed, and oversized frames to test parser robustness
  ctx.uart.push_rx({// ─────────────────────────────
                    // Noise (no 0xFC) -> should be ignored via preamble reset
                    // ────────────────────────────
                    0x01, 0x02, 0x03, 0x04, 0x05,

                    // ─────────────────────────────
                    // Partial frame (declares payload len=5, but we cut it short)
                    // Later bytes will eventually force checksum mismatch and reset
                    // ─────────────────────────────
                    0xFC, 0x62, 0x01, 0x30, 0x05, 0xAA, 0xBB,

                    // ─────────────────────────────
                    // Invalid header (header byte 3 should be 0x01, header byte 4 should be 0x30)
                    // Should reset quickly on header mismatch
                    // ─────────────────────────────
                    0xFC, 0x62, 0xFF, 0xFF, 0x02, 0x01, 0x02, 0x00,

                    // ─────────────────────────────
                    // Oversized length field (rejected by payload-too-large check at HEADER_LEN)
                    // ─────────────────────────────
                    0xFC, 0x62, 0x01, 0x30, 0xFE, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1A,
                    0x1B, 0x1C, 0x1D, 0x1E, 0x1F,

                    // ─────────────────────────────
                    // Valid unknown-type frame (type=0x62), should be parsed successfully then ignored
                    // Frame: FC 62 01 30 02 AA BB 30
                    // ─────────────────────────────
                    0xFC, 0x62, 0x01, 0x30, 0x02, 0xAA, 0xBB, 0x30,

                    // ─────────────────────────────
                    // Invalid checksum (should be rejected at checksum check)
                    // ─────────────────────────────
                    0xFC, 0x62, 0x01, 0x30, 0x02, 0x10, 0x20, 0xFF,

                    // ─────────────────────────────
                    // Back-to-back VALID frames (unknown type=0x62) to stress boundary handling.
                    // Frame A: FC 62 01 30 01 02 6C
                    // Frame B: FC 62 01 30 01 03 6B
                    // ─────────────────────────────
                    0xFC, 0x62, 0x01, 0x30, 0x01, 0x02, 0x6C, 0xFC, 0x62, 0x01, 0x30, 0x01, 0x03, 0x6B,

                    // ─────────────────────────────
                    // Trailing noise
                    // ─────────────────────────────
                    0x55, 0x66, 0x77, 0x88});

  // Drain RX - no valid response, no state change expected
  int iterations = 0;
  while (!ctx.uart.rx.empty() && iterations++ < 10) {
    ASSERT_FALSE(ctx.sut.update());
    EXPECT_EQ(ctx.sut.state_, TestableMitsubishiCN105::State::CONNECTING);
    EXPECT_TRUE(ctx.uart.tx.empty());
  }

  EXPECT_TRUE(ctx.uart.rx.empty());
}

TEST(MitsubishiCN105Tests, NextStatusUpdateAfterUpdateIntervalMilliseconds) {
  auto ctx = TestContext{};

  ctx.sut.set_update_interval(2000);
  ctx.sut.set_current_time(80000);

  // Status update completed, schedule next status update
  ctx.sut.state_ = TestableMitsubishiCN105::State::STATUS_UPDATED;
  ctx.sut.set_state(TestableMitsubishiCN105::State::SCHEDULE_NEXT_STATUS_UPDATE);

  EXPECT_EQ(ctx.sut.state_, TestableMitsubishiCN105::State::WAITING_FOR_SCHEDULED_STATUS_UPDATE);
  EXPECT_EQ(ctx.sut.operation_start_ms_, 80000);

  // Wait for update_interval (ms) before doing another status update
  ASSERT_FALSE(ctx.sut.update());
  EXPECT_TRUE(ctx.uart.tx.empty());
  EXPECT_EQ(ctx.sut.state_, TestableMitsubishiCN105::State::WAITING_FOR_SCHEDULED_STATUS_UPDATE);

  ctx.sut.set_current_time(81999);
  ASSERT_FALSE(ctx.sut.update());
  EXPECT_TRUE(ctx.uart.tx.empty());
  EXPECT_EQ(ctx.sut.state_, TestableMitsubishiCN105::State::WAITING_FOR_SCHEDULED_STATUS_UPDATE);

  ctx.sut.set_current_time(82000);
  ASSERT_FALSE(ctx.sut.update());
  EXPECT_FALSE(ctx.uart.tx.empty());
  EXPECT_EQ(ctx.sut.state_, TestableMitsubishiCN105::State::UPDATING_STATUS);
  EXPECT_EQ(ctx.sut.operation_start_ms_, 82000);
}

TEST(MitsubishiCN105Tests, DecodeStatusSettingsPackageTempEncodedA) {
  auto ctx = TestContext{};

  ctx.uart.push_rx(
      {0xFC, 0x62, 0x01, 0x30, 0x0C, 0x02, 0x00, 0x00, 0x01, 0x03, 0x05, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x55});

  ctx.sut.update();

  EXPECT_TRUE(ctx.sut.status().power_on);
  EXPECT_FALSE(ctx.sut.use_temperature_encoding_b_);
  EXPECT_EQ(ctx.sut.status().target_temperature, 26.0f);
  EXPECT_EQ(ctx.sut.status().mode, MitsubishiCN105::Mode::COOL);
  EXPECT_EQ(ctx.sut.status().fan_mode, MitsubishiCN105::FanMode::QUIET);
}

TEST(MitsubishiCN105Tests, DecodeStatusSettingsPackageTempEncodedB) {
  auto ctx = TestContext{};

  ctx.uart.push_rx(
      {0xFC, 0x62, 0x01, 0x30, 0x0C, 0x02, 0x00, 0x00, 0x00, 0x07, 0x00, 0x06, 0x00, 0x00, 0x00, 0x00, 0xA5, 0xAD});

  ctx.sut.update();

  EXPECT_FALSE(ctx.sut.status().power_on);
  EXPECT_TRUE(ctx.sut.use_temperature_encoding_b_);
  EXPECT_EQ(ctx.sut.status().target_temperature, 18.5f);
  EXPECT_EQ(ctx.sut.status().mode, MitsubishiCN105::Mode::FAN_ONLY);
  EXPECT_EQ(ctx.sut.status().fan_mode, MitsubishiCN105::FanMode::SPEED_4);
}

TEST(MitsubishiCN105Tests, DecodeStatusRoomTempPackageTempEncodedA) {
  auto ctx = TestContext{};

  ctx.uart.push_rx({0xFC, 0x62, 0x01, 0x30, 0x07, 0x03, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x5D});

  ctx.sut.update();

  EXPECT_EQ(ctx.sut.status().room_temperature, 16.0f);
}

TEST(MitsubishiCN105Tests, DecodeStatusRoomTempPackageTempEncodedB) {
  auto ctx = TestContext{};

  ctx.uart.push_rx({0xFC, 0x62, 0x01, 0x30, 0x07, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0xBC, 0xA7});

  ctx.sut.update();

  EXPECT_EQ(ctx.sut.status().room_temperature, 30.0f);
}

TEST(MitsubishiCN105Tests, DecodeWideVanePackageHighBitNotSet) {
  auto ctx = TestContext{};

  ctx.uart.push_rx({0xFC, 0x62, 0x01, 0x30, 0x10, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00,
                    0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x58});

  ctx.sut.update();

  EXPECT_EQ(ctx.sut.status().wide_vane_mode, MitsubishiCN105::WideVaneMode::CENTER);
  EXPECT_FALSE(ctx.sut.set_wide_vane_high_bit_);
}

TEST(MitsubishiCN105Tests, DecodeWideVanePackageHighBitSet) {
  auto ctx = TestContext{};

  ctx.uart.push_rx({0xFC, 0x62, 0x01, 0x30, 0x10, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00,
                    0x00, 0x00, 0x00, 0x00, 0x83, 0x00, 0x00, 0x00, 0x00, 0x00, 0xD8});

  ctx.sut.update();

  EXPECT_EQ(ctx.sut.status().wide_vane_mode, MitsubishiCN105::WideVaneMode::CENTER);
  EXPECT_TRUE(ctx.sut.set_wide_vane_high_bit_);
}

TEST(MitsubishiCN105Tests, ApplySettingsPowerOn) {
  auto ctx = TestContext{};

  ctx.sut.set_power(true);
  ctx.sut.apply_settings();

  EXPECT_THAT(ctx.uart.tx, ::testing::ElementsAre(0xFC, 0x41, 0x01, 0x30, 0x10, 0x01, 0x01, 0x00, 0x01, 0x00, 0x00,
                                                  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7B));
}

TEST(MitsubishiCN105Tests, ApplySettingsTemperatureEncodedA) {
  auto ctx = TestContext{};

  ctx.sut.set_target_temperature(23.0f);
  ctx.sut.apply_settings();

  EXPECT_THAT(ctx.uart.tx, ::testing::ElementsAre(0xFC, 0x41, 0x01, 0x30, 0x10, 0x01, 0x04, 0x00, 0x00, 0x00, 0x08,
                                                  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x71));
}

TEST(MitsubishiCN105Tests, ApplySettingsTemperatureEncodedB) {
  auto ctx = TestContext{};

  ctx.sut.use_temperature_encoding_b_ = true;
  ctx.sut.set_target_temperature(26.0f);
  ctx.sut.apply_settings();

  EXPECT_THAT(ctx.uart.tx, ::testing::ElementsAre(0xFC, 0x41, 0x01, 0x30, 0x10, 0x01, 0x04, 0x00, 0x00, 0x00, 0x00,
                                                  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xB4, 0x00, 0xC5));
}

TEST(MitsubishiCN105Tests, ApplySettingsHalfDegreeTemperatureEncodedB) {
  auto ctx = TestContext{};

  ctx.sut.use_temperature_encoding_b_ = true;
  ctx.sut.set_target_temperature(26.5f);
  ctx.sut.apply_settings();

  EXPECT_THAT(ctx.uart.tx, ::testing::ElementsAre(0xFC, 0x41, 0x01, 0x30, 0x10, 0x01, 0x04, 0x00, 0x00, 0x00, 0x00,
                                                  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xB5, 0x00, 0xC4));
}

TEST(MitsubishiCN105Tests, ApplyModeCool) {
  auto ctx = TestContext{};

  ctx.sut.set_mode(MitsubishiCN105::Mode::COOL);
  ctx.sut.apply_settings();

  EXPECT_THAT(ctx.uart.tx, ::testing::ElementsAre(0xFC, 0x41, 0x01, 0x30, 0x10, 0x01, 0x02, 0x00, 0x00, 0x03, 0x00,
                                                  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x78));
}

TEST(MitsubishiCN105Tests, ApplyFanModeSpeed1) {
  auto ctx = TestContext{};

  ctx.sut.set_fan_mode(MitsubishiCN105::FanMode::SPEED_1);
  ctx.sut.apply_settings();

  EXPECT_THAT(ctx.uart.tx, ::testing::ElementsAre(0xFC, 0x41, 0x01, 0x30, 0x10, 0x01, 0x08, 0x00, 0x00, 0x00, 0x00,
                                                  0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x73));
}

TEST(MitsubishiCN105Tests, ApplyVaneModeSwing) {
  auto ctx = TestContext{};

  ctx.sut.set_vane_mode(MitsubishiCN105::VaneMode::SWING);
  ctx.sut.apply_settings();

  EXPECT_THAT(ctx.uart.tx, ::testing::ElementsAre(0xFC, 0x41, 0x01, 0x30, 0x10, 0x01, 0x10, 0x00, 0x00, 0x00, 0x00,
                                                  0x00, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x66));
}

TEST(MitsubishiCN105Tests, ApplyWideVaneModeLeftAndHighBitNotSet) {
  auto ctx = TestContext{};

  ctx.sut.set_wide_vane_mode(MitsubishiCN105::WideVaneMode::LEFT);
  ctx.sut.apply_settings();

  EXPECT_THAT(ctx.uart.tx, ::testing::ElementsAre(0xFC, 0x41, 0x01, 0x30, 0x10, 0x01, 0x00, 0x01, 0x00, 0x00, 0x00,
                                                  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x7A));
}

TEST(MitsubishiCN105Tests, ApplyWideVaneModeLeftAndHighBitSet) {
  auto ctx = TestContext{};

  ctx.sut.set_wide_vane_high_bit_ = true;
  ctx.sut.set_wide_vane_mode(MitsubishiCN105::WideVaneMode::LEFT);
  ctx.sut.apply_settings();

  EXPECT_THAT(ctx.uart.tx, ::testing::ElementsAre(0xFC, 0x41, 0x01, 0x30, 0x10, 0x01, 0x00, 0x01, 0x00, 0x00, 0x00,
                                                  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x82, 0x00, 0x00, 0xFA));
}

TEST(MitsubishiCN105Tests, WriteInterruptsWaitingForNextStatusUpdate) {
  auto ctx = TestContext{};

  ctx.sut.set_update_interval(2000);
  ctx.sut.set_current_time(5000);

  // Waiting for next scheduled status update
  ctx.sut.state_ = TestableMitsubishiCN105::State::STATUS_UPDATED;
  ctx.sut.set_state(TestableMitsubishiCN105::State::SCHEDULE_NEXT_STATUS_UPDATE);
  EXPECT_EQ(ctx.sut.state_, TestableMitsubishiCN105::State::WAITING_FOR_SCHEDULED_STATUS_UPDATE);
  EXPECT_EQ(ctx.sut.operation_start_ms_, 5000);
  EXPECT_EQ(ctx.sut.status_update_wait_credit_ms_, 0);

  // Nothing to do in update (rx empty, no timeout)
  ctx.sut.set_current_time(5500);
  ASSERT_FALSE(ctx.sut.update());
  EXPECT_TRUE(ctx.uart.tx.empty());
  EXPECT_EQ(ctx.sut.operation_start_ms_, 5000);
  EXPECT_EQ(ctx.sut.status_update_wait_credit_ms_, 0);

  // Write new values
  ctx.sut.use_temperature_encoding_b_ = true;
  ctx.sut.set_power(false);
  ctx.sut.set_target_temperature(25.0f);
  ctx.sut.set_mode(MitsubishiCN105::Mode::HEAT);
  ctx.sut.set_fan_mode(MitsubishiCN105::FanMode::AUTO);
  ctx.sut.set_vane_mode(MitsubishiCN105::VaneMode::AUTO);

  // Waiting for next status update must be interrupted and new values send to AC
  ctx.sut.set_current_time(6000);
  ASSERT_FALSE(ctx.sut.update());
  EXPECT_EQ(ctx.sut.status_update_wait_credit_ms_, 1000);
  EXPECT_EQ(ctx.sut.state_, TestableMitsubishiCN105::State::APPLYING_SETTINGS);
  EXPECT_THAT(ctx.uart.tx, ::testing::ElementsAre(0xFC, 0x41, 0x01, 0x30, 0x10, 0x01, 0x1F, 0x00, 0x00, 0x01, 0x00,
                                                  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xB2, 0x00, 0xAB));
  // Write ACK response
  ctx.uart.push_rx({0xFC, 0x61, 0x01, 0x30, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
                    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x5E});
  ctx.sut.set_current_time(6500);
  ASSERT_FALSE(ctx.sut.update());
  EXPECT_EQ(ctx.sut.state_, TestableMitsubishiCN105::State::WAITING_FOR_SCHEDULED_STATUS_UPDATE);
  EXPECT_EQ(ctx.sut.operation_start_ms_, 6500 - 1000);
  EXPECT_EQ(ctx.sut.status_update_wait_credit_ms_, 0);
}

TEST(MitsubishiCN105Tests, SetAndClearRemoteRoomTemp) {
  auto ctx = TestContext{};

  // Set remote temperature
  ctx.sut.set_remote_temperature(28.5f);

  ctx.sut.state_ = TestableMitsubishiCN105::State::WAITING_FOR_SCHEDULED_STATUS_UPDATE;
  ctx.sut.set_state(TestableMitsubishiCN105::State::APPLYING_SETTINGS);

  EXPECT_THAT(ctx.uart.tx, ::testing::ElementsAre(0xFC, 0x41, 0x01, 0x30, 0x10, 0x07, 0x01, 0x29, 0xB9, 0x00, 0x00,
                                                  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x94));

  // Write ACK response
  ctx.uart.push_rx({0xFC, 0x61, 0x01, 0x30, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
                    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x5E});
  ASSERT_FALSE(ctx.sut.update());
  EXPECT_EQ(ctx.sut.state_, TestableMitsubishiCN105::State::WAITING_FOR_SCHEDULED_STATUS_UPDATE);

  ctx.uart.tx.clear();

  // Clear remote temperature
  ctx.sut.clear_remote_temperature();

  ctx.sut.set_state(TestableMitsubishiCN105::State::APPLYING_SETTINGS);

  EXPECT_THAT(ctx.uart.tx, ::testing::ElementsAre(0xFC, 0x41, 0x01, 0x30, 0x10, 0x07, 0x00, 0x00, 0x80, 0x00, 0x00,
                                                  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF7));

  // Write ACK response
  ctx.uart.push_rx({0xFC, 0x61, 0x01, 0x30, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
                    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x5E});
  ASSERT_FALSE(ctx.sut.update());
  EXPECT_EQ(ctx.sut.state_, TestableMitsubishiCN105::State::WAITING_FOR_SCHEDULED_STATUS_UPDATE);
}

TEST(MitsubishiCN105Tests, ApplyQueuedSettingsThenRemoteRoomTempInSecondWrite) {
  auto ctx = TestContext{};

  // Queue normal settings plus remote temperature together.
  ctx.sut.use_temperature_encoding_b_ = true;
  ctx.sut.set_power(false);
  ctx.sut.set_target_temperature(25.0f);
  ctx.sut.set_mode(MitsubishiCN105::Mode::HEAT);
  ctx.sut.set_fan_mode(MitsubishiCN105::FanMode::AUTO);
  ctx.sut.set_remote_temperature(28.5f);

  // First apply sends only the normal settings write.
  ctx.sut.state_ = TestableMitsubishiCN105::State::WAITING_FOR_SCHEDULED_STATUS_UPDATE;
  ctx.sut.set_state(TestableMitsubishiCN105::State::APPLYING_SETTINGS);

  EXPECT_THAT(ctx.uart.tx, ::testing::ElementsAre(0xFC, 0x41, 0x01, 0x30, 0x10, 0x01, 0x0F, 0x00, 0x00, 0x01, 0x00,
                                                  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xB2, 0x00, 0xBB));
  EXPECT_TRUE(ctx.sut.pending_updates_.contains(TestableMitsubishiCN105::UpdateFlag::REMOTE_TEMPERATURE));
  EXPECT_FALSE(ctx.sut.pending_updates_.contains(TestableMitsubishiCN105::UpdateFlag::POWER));
  EXPECT_FALSE(ctx.sut.pending_updates_.contains(TestableMitsubishiCN105::UpdateFlag::TEMPERATURE));
  EXPECT_FALSE(ctx.sut.pending_updates_.contains(TestableMitsubishiCN105::UpdateFlag::MODE));
  EXPECT_FALSE(ctx.sut.pending_updates_.contains(TestableMitsubishiCN105::UpdateFlag::FAN));

  // ACK the first write. Remote temperature should still be pending afterward.
  ctx.uart.tx.clear();
  ctx.uart.push_rx({0xFC, 0x61, 0x01, 0x30, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
                    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x5E});
  ASSERT_FALSE(ctx.sut.update());

  EXPECT_TRUE(ctx.sut.pending_updates_.contains(TestableMitsubishiCN105::UpdateFlag::REMOTE_TEMPERATURE));

  // The next apply sends the remote-temperature packet and clears the last pending flag.
  ctx.uart.tx.clear();
  ctx.sut.set_state(TestableMitsubishiCN105::State::APPLYING_SETTINGS);

  EXPECT_THAT(ctx.uart.tx, ::testing::ElementsAre(0xFC, 0x41, 0x01, 0x30, 0x10, 0x07, 0x01, 0x29, 0xB9, 0x00, 0x00,
                                                  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x94));
  EXPECT_FALSE(ctx.sut.pending_updates_.any());
}

TEST(MitsubishiCN105Tests, WriteTimeoutClearsStatusUpdateWaitCreditOnReconnect) {
  auto ctx = TestContext{};
  ctx.sut.set_update_interval(2000);
  ctx.sut.set_current_time(5000);

  // Start in the scheduled status update wait state.
  ctx.sut.state_ = TestableMitsubishiCN105::State::STATUS_UPDATED;
  ctx.sut.set_state(TestableMitsubishiCN105::State::SCHEDULE_NEXT_STATUS_UPDATE);
  ASSERT_EQ(ctx.sut.state_, TestableMitsubishiCN105::State::WAITING_FOR_SCHEDULED_STATUS_UPDATE);
  ASSERT_EQ(ctx.sut.operation_start_ms_, 5000);
  ASSERT_EQ(ctx.sut.status_update_wait_credit_ms_, 0);

  // Interrupt that wait with a write so credit is accumulated.
  ctx.sut.use_temperature_encoding_b_ = true;
  ctx.sut.set_power(false);
  ctx.sut.set_target_temperature(25.0f);
  ctx.sut.set_mode(MitsubishiCN105::Mode::HEAT);
  ctx.sut.set_fan_mode(MitsubishiCN105::FanMode::AUTO);
  ctx.sut.set_current_time(6000);
  ASSERT_FALSE(ctx.sut.update());
  ASSERT_EQ(ctx.sut.state_, TestableMitsubishiCN105::State::APPLYING_SETTINGS);
  ASSERT_EQ(ctx.sut.operation_start_ms_, 6000);
  ASSERT_EQ(ctx.sut.status_update_wait_credit_ms_, 1000);

  // Do not ACK the write. Advance time far enough to force timeout/reconnect
  // handling and verify that stale wait credit is cleared during recovery.
  ctx.sut.set_current_time(36000);
  ASSERT_FALSE(ctx.sut.update());
  EXPECT_NE(ctx.sut.state_, TestableMitsubishiCN105::State::APPLYING_SETTINGS);
  ASSERT_EQ(ctx.sut.operation_start_ms_, 36000);
  EXPECT_EQ(ctx.sut.status_update_wait_credit_ms_, 0);
}

TEST(MitsubishiCN105Tests, SetOutOfRangeRemoteRoomTempIsIgnored) {
  auto ctx = TestContext{};

  ctx.sut.set_remote_temperature(7.0f);
  EXPECT_FALSE(ctx.sut.pending_updates_.contains(TestableMitsubishiCN105::UpdateFlag::REMOTE_TEMPERATURE));

  ctx.sut.set_remote_temperature(40.0f);
  EXPECT_FALSE(ctx.sut.pending_updates_.contains(TestableMitsubishiCN105::UpdateFlag::REMOTE_TEMPERATURE));

  ctx.sut.set_remote_temperature(NAN);
  EXPECT_FALSE(ctx.sut.pending_updates_.contains(TestableMitsubishiCN105::UpdateFlag::REMOTE_TEMPERATURE));
}

TEST(MitsubishiCN105Tests, SetMinRemoteRoomTemp) {
  auto ctx = TestContext{};
  ctx.sut.set_remote_temperature(8.0f);
  EXPECT_TRUE(ctx.sut.pending_updates_.contains(TestableMitsubishiCN105::UpdateFlag::REMOTE_TEMPERATURE));
}

TEST(MitsubishiCN105Tests, SetMaxRemoteRoomTemp) {
  auto ctx = TestContext{};
  ctx.sut.set_remote_temperature(39.5f);
  EXPECT_TRUE(ctx.sut.pending_updates_.contains(TestableMitsubishiCN105::UpdateFlag::REMOTE_TEMPERATURE));
}

}  // namespace esphome::mitsubishi_cn105::testing
