"""Deye TOU SOC % podle fyzického režimu (SELL vs PASSIVE)."""

from __future__ import annotations

import unittest
from dataclasses import replace

from services.control.exporter_monolith import (
    ControlSetpoints,
    InverterConfig,
    _deye_reg178_verify_with_double_read,
    _deye_tou_params,
    _deye_tou_power_verify_match,
    deye_mi_export_cutoff_want_enabled,
    deye_reg_triggers_self_sustain_after_verify_exhaust,
    get_deye_mode,
)
from services.control.models import OperatingModeInfo
from services.control.setpoints import (
    _build_setpoints,
    _deye_zero_export_amps_for_passive,
)


def _inv(*, min_soc: int | None = 12, reserve_soc: int | None = 20) -> InverterConfig:
    return InverterConfig(
        id=1,
        code="deye-main",
        host="127.0.0.1",
        port=502,
        unit_id=1,
        max_export_power_w=13_500,
        max_import_power_w=13_500,
        no_export=False,
        max_battery_charge_w=10_000,
        max_battery_discharge_w=10_000,
        min_soc_percent=min_soc,
        reserve_soc_percent=reserve_soc,
        max_soc_percent=95,
        usable_capacity_wh=64_000,
        max_charge_a=100,
        max_discharge_a=100,
    )


def _inv_350a() -> InverterConfig:
    """350 A × 51.2 V = 17920 W — typický firmware clamp pro TOU power."""
    return replace(_inv(), max_charge_a=350, max_discharge_a=350)


class ModbusVerifyPolicyTests(unittest.TestCase):
    def test_tou_power_accepts_firmware_max_w_clamp(self) -> None:
        inv = _inv_350a()
        self.assertTrue(_deye_tou_power_verify_match(7752, 17920, inv))
        self.assertTrue(_deye_tou_power_verify_match(16728, 17920, inv))

    def test_reg178_double_read_recovers_from_glitch(self) -> None:
        ok, v = _deye_reg178_verify_with_double_read(48, 12014, 48)
        self.assertTrue(ok)
        self.assertEqual(v, 48)

    def test_reg178_not_critical_for_self_sustain(self) -> None:
        self.assertFalse(deye_reg_triggers_self_sustain_after_verify_exhaust(178))

    def test_reg108_critical_for_self_sustain(self) -> None:
        self.assertTrue(deye_reg_triggers_self_sustain_after_verify_exhaust(108))


class DeyeTouParamsTests(unittest.TestCase):
    def test_sell_uses_reserve_soc(self) -> None:
        """SELL: záporný grid_setpoint_w i battery_w → selling first; TOU SOC = reserve."""
        sp = ControlSetpoints(
            battery_w=-8000,
            grid_export_limit=8000,
            ev1_current_a=0,
            ev2_current_a=0,
            heat_pump_enable=False,
            grid_setpoint_w=-8000,
            ev1_power_w=0,
            ev2_power_w=0,
            target_soc_pct=50,
        )
        self.assertEqual(get_deye_mode(sp), "SELL")
        p, s, g = _deye_tou_params(sp, _inv())
        self.assertFalse(g)
        self.assertEqual(s, 20)

    def test_explicit_deye_physical_mode_from_plan_overrides_detection(self) -> None:
        sp = ControlSetpoints(
            battery_w=-8000,
            grid_export_limit=8000,
            ev1_current_a=0,
            ev2_current_a=0,
            heat_pump_enable=False,
            grid_setpoint_w=-8000,
            ev1_power_w=0,
            ev2_power_w=0,
            target_soc_pct=50,
            deye_physical_mode="PASSIVE",
        )
        self.assertEqual(get_deye_mode(sp), "PASSIVE")

    def test_export_ban_does_not_change_deye_mode(self) -> None:
        sp = ControlSetpoints(
            battery_w=0,
            grid_export_limit=0,
            ev1_current_a=0,
            ev2_current_a=0,
            heat_pump_enable=False,
            grid_setpoint_w=0,
            ev1_power_w=0,
            ev2_power_w=0,
            target_soc_pct=50,
            export_ban=True,
        )
        self.assertEqual(get_deye_mode(sp), "PASSIVE")

    def test_mi_export_cutoff_on_export_ban_without_plan_flag(self) -> None:
        self.assertTrue(
            deye_mi_export_cutoff_want_enabled(
                gen_microinverter_cutoff_enabled=True,
                deye_gen_cutoff_enabled=False,
                export_ban=True,
                deye_mode="PASSIVE",
            )
        )

    def test_mi_export_cutoff_off_when_sell_mode(self) -> None:
        self.assertFalse(
            deye_mi_export_cutoff_want_enabled(
                gen_microinverter_cutoff_enabled=True,
                deye_gen_cutoff_enabled=True,
                export_ban=True,
                deye_mode="SELL",
            )
        )

    def test_mi_export_cutoff_off_without_feature_flag(self) -> None:
        self.assertFalse(
            deye_mi_export_cutoff_want_enabled(
                gen_microinverter_cutoff_enabled=False,
                deye_gen_cutoff_enabled=True,
                export_ban=True,
                deye_mode="PASSIVE",
            )
        )

    def test_build_setpoints_uses_explicit_export_limit(self) -> None:
        mode = OperatingModeInfo(
            mode_code="AUTO",
            battery_mode="AUTO",
            grid_mode="AUTO",
            ev_enabled=False,
            heat_pump_enabled_def=False,
            loxone_mode_value=1,
        )
        pi = {
            "battery_setpoint_w": 0,
            "grid_setpoint_w": -3000,
            "export_limit_w": 13_500,
            "export_mode": "PV_SURPLUS",
            "ev1_setpoint_w": 0,
            "ev2_setpoint_w": 0,
            "heat_pump_enabled": False,
            "battery_soc_target_pct": 50,
            "effective_sell_price": 1.0,
        }
        sp = _build_setpoints(mode, pi)
        self.assertIsNotNone(sp)
        self.assertEqual(sp.grid_export_limit, 13_500)

    def test_pv_led_export_with_small_battery_is_sell(self) -> None:
        """Obě záporné → SELL (bez porovnání |bat| vs |grid|)."""
        sp = ControlSetpoints(
            battery_w=-733,
            grid_export_limit=1294,
            ev1_current_a=0,
            ev2_current_a=0,
            heat_pump_enable=False,
            grid_setpoint_w=-1294,
            ev1_power_w=0,
            ev2_power_w=0,
            target_soc_pct=50,
        )
        self.assertEqual(get_deye_mode(sp), "SELL")

    def test_large_export_small_battery_is_sell(self) -> None:
        """I když |bat| < |grid| — stále SELL při obou záporných setpointech."""
        sp = ControlSetpoints(
            battery_w=-1500,
            grid_export_limit=8000,
            ev1_current_a=0,
            ev2_current_a=0,
            heat_pump_enable=False,
            grid_setpoint_w=-8000,
            ev1_power_w=0,
            ev2_power_w=0,
            target_soc_pct=50,
        )
        self.assertEqual(get_deye_mode(sp), "SELL")

    def test_passive_uses_min_soc(self) -> None:
        sp = ControlSetpoints(
            battery_w=0,
            grid_export_limit=0,
            ev1_current_a=0,
            ev2_current_a=0,
            heat_pump_enable=False,
            grid_setpoint_w=0,
            ev1_power_w=0,
            ev2_power_w=0,
            target_soc_pct=None,
            effective_sell_price_czk_kwh=None,
        )
        self.assertEqual(get_deye_mode(sp), "PASSIVE")
        p, s, g = _deye_tou_params(sp, _inv(min_soc=12, reserve_soc=20))
        self.assertFalse(g)
        self.assertEqual(s, 12)

    def test_passive_negative_sell_tou_stays_min_soc(self) -> None:
        """PASSIVE: záporná vykupní nenastavuje TOU na 100 — zůstává min_soc (145/export_ban řeší síť)."""
        sp = ControlSetpoints(
            battery_w=-400,
            grid_export_limit=0,
            ev1_current_a=0,
            ev2_current_a=0,
            heat_pump_enable=False,
            grid_setpoint_w=0,
            ev1_power_w=0,
            ev2_power_w=0,
            target_soc_pct=14,
            effective_sell_price_czk_kwh=-0.25,
        )
        self.assertEqual(get_deye_mode(sp), "PASSIVE")
        _p, s, g = _deye_tou_params(sp, _inv(min_soc=12, reserve_soc=20))
        self.assertFalse(g)
        self.assertEqual(s, 12)

    def test_passive_planned_pv_charge_tou_stays_min_soc(self) -> None:
        """PASSIVE s kladným battery_w bez grid importu: CHARGE to není — TOU je stále min_soc."""
        sp = ControlSetpoints(
            battery_w=800,
            grid_export_limit=0,
            ev1_current_a=0,
            ev2_current_a=0,
            heat_pump_enable=False,
            grid_setpoint_w=0,
            ev1_power_w=0,
            ev2_power_w=0,
            target_soc_pct=60,
            effective_sell_price_czk_kwh=1.0,
        )
        self.assertEqual(get_deye_mode(sp), "PASSIVE")
        _p, s, g = _deye_tou_params(sp, _inv(min_soc=12, reserve_soc=20))
        self.assertFalse(g)
        self.assertEqual(s, 12)

    def test_charge_unchanged_grid_charge(self) -> None:
        sp = ControlSetpoints(
            battery_w=5000,
            grid_export_limit=0,
            ev1_current_a=0,
            ev2_current_a=0,
            heat_pump_enable=False,
            grid_setpoint_w=5000,
            ev1_power_w=0,
            ev2_power_w=0,
            target_soc_pct=80,
        )
        self.assertEqual(get_deye_mode(sp), "CHARGE")
        _p, s, g = _deye_tou_params(sp, _inv())
        self.assertTrue(g)
        self.assertEqual(s, 95)

    def test_charge_target_soc_respects_max_soc_100(self) -> None:
        sp = ControlSetpoints(
            battery_w=5000,
            grid_export_limit=0,
            ev1_current_a=0,
            ev2_current_a=0,
            heat_pump_enable=False,
            grid_setpoint_w=5000,
            ev1_power_w=0,
            ev2_power_w=0,
            target_soc_pct=80,
        )
        self.assertEqual(get_deye_mode(sp), "CHARGE")
        inv = replace(_inv(), max_soc_percent=100)
        _p, s, g = _deye_tou_params(sp, inv)
        self.assertTrue(g)
        self.assertEqual(s, 100)

    def test_charge_any_positive_pair_without_w_threshold(self) -> None:
        sp = ControlSetpoints(
            battery_w=50,
            grid_export_limit=0,
            ev1_current_a=0,
            ev2_current_a=0,
            heat_pump_enable=False,
            grid_setpoint_w=80,
            ev1_power_w=0,
            ev2_power_w=0,
            target_soc_pct=50,
        )
        self.assertEqual(get_deye_mode(sp), "CHARGE")

    def test_zero_export_amps_fve_overflow(self) -> None:
        c, d = _deye_zero_export_amps_for_passive(-1000, 0, 100, 90)
        self.assertEqual(c, 100)
        self.assertEqual(d, 90)

    def test_zero_export_amps_import_hold_discharge(self) -> None:
        c, d = _deye_zero_export_amps_for_passive(500, 0, 100, 90)
        self.assertEqual(c, 100)
        self.assertEqual(d, 0)

    def test_zero_export_amps_full_when_discharge_with_export(self) -> None:
        """Export + plánované vybíjení → plné proudy (SELL řeší režim 142 zvlášť)."""
        c, d = _deye_zero_export_amps_for_passive(-2000, -500, 100, 90)
        self.assertEqual(c, 100)
        self.assertEqual(d, 90)

    def test_self_sustain_tou_stays_min_soc_even_if_sell_negative(self) -> None:
        """SELF_SUSTAIN: nízké TOU (min_soc), ne 100 % z negativní vykupní — LP se nepoužívá."""
        sp = ControlSetpoints(
            battery_w=None,
            grid_export_limit=0,
            ev1_current_a=0,
            ev2_current_a=0,
            heat_pump_enable=False,
            grid_setpoint_w=0,
            ev1_power_w=0,
            ev2_power_w=0,
            target_soc_pct=None,
            effective_sell_price_czk_kwh=-0.48,
            self_sustain_local_use=True,
        )
        self.assertEqual(get_deye_mode(sp), "PASSIVE")
        _p, s, g = _deye_tou_params(sp, _inv(min_soc=12, reserve_soc=20))
        self.assertFalse(g)
        self.assertEqual(s, 12)

    def test_lock_battery_uses_min_soc(self) -> None:
        sp = ControlSetpoints(
            battery_w=0,
            grid_export_limit=0,
            ev1_current_a=0,
            ev2_current_a=0,
            heat_pump_enable=False,
            grid_setpoint_w=-500,
            ev1_power_w=0,
            ev2_power_w=0,
            target_soc_pct=None,
            lock_battery=True,
        )
        p, s, g = _deye_tou_params(sp, _inv(min_soc=12))
        self.assertEqual(p, 0)
        self.assertFalse(g)
        self.assertEqual(s, 12)


if __name__ == "__main__":
    unittest.main()
