"""Battery charge/discharge power (W) from Anker cloud/scene API (no MQTT)."""

from __future__ import annotations

from typing import Any

from entities import _parse_power_value


def _parse_signed(val: Any) -> int | None:
    if val is None or val == "":
        return None
    parsed = _parse_power_value(val)
    if isinstance(parsed, int):
        return parsed
    try:
        return int(float(val))
    except (TypeError, ValueError):
        return None


def _max_positive(*values: int | None) -> int:
    nums = [v for v in values if isinstance(v, int) and v > 0]
    return max(nums) if nums else 0


_BATTERY_POWER_KEYS: frozenset[str] = frozenset(
    {
        "charging_power",
        "bat_charge_power",
        "bat_discharge_power",
        "battery_charge_power",
        "battery_discharge_power",
        "battery_to_home_power",
        "bat_to_home_power",
        "pv_to_battery_power",
        "solar_to_battery_power",
        "grid_to_battery_power",
        "battery_to_grid_power",
        "output_power",
        "dc_output_power",
        "photovoltaic_power",
        "input_power",
    }
)


def _coalesce_power_field(key: str, device_val: Any, scene_val: Any) -> Any:
    """Prefer device cache; scene often sends 0 for bat_* while discharging."""
    p = _parse_signed(device_val)
    s = _parse_signed(scene_val)
    if key == "charging_power":
        if p is not None and p != 0:
            return device_val
        if s is not None and s != 0:
            return scene_val
        return device_val if device_val not in (None, "") else scene_val
    if p is not None and p > 0:
        return device_val
    if s is not None and s > 0:
        return scene_val
    return device_val if device_val not in (None, "") else scene_val


def _pick_from_output_balance(data: dict) -> tuple[int, int]:
    """Infer charge/discharge when cloud bat_* are 0 but AC output differs from PV."""
    pv = _parse_signed(data.get("photovoltaic_power")) or _parse_signed(
        data.get("input_power")
    )
    out = _parse_signed(data.get("output_power")) or _parse_signed(
        data.get("dc_output_power")
    )
    if out is None:
        return 0, 0
    grid = _parse_signed(data.get("grid_to_battery_power")) or 0
    pv_val = pv if pv is not None else 0
    calc = pv_val + grid - out
    if calc > 0:
        return calc, 0
    if calc < 0:
        discharge = abs(calc)
        if _looks_like_export_not_battery_discharge(data, discharge):
            return 0, 0
        return 0, discharge
    return 0, 0


def _has_battery_flow_fields(data: dict) -> bool:
    """True when a battery-related flow sensor reports active power (W > 0)."""
    keys = (
        "pv_to_battery_power",
        "solar_to_battery_power",
        "battery_to_home_power",
        "bat_to_home_power",
        "grid_to_battery_power",
        "battery_to_grid_power",
    )
    return any(
        (parsed := _parse_signed(data.get(k))) is not None and parsed > 0 for k in keys
    )


def _pick_from_power_flows(data: dict) -> tuple[int, int]:
    """Charge/discharge from flow sensors only (no cloud bat_* totals)."""
    charge = _max_positive(
        _parse_signed(data.get("pv_to_battery_power")),
        _parse_signed(data.get("solar_to_battery_power")),
        _parse_signed(data.get("grid_to_battery_power")),
    )
    discharge = _max_positive(
        _parse_signed(data.get("battery_to_home_power")),
        _parse_signed(data.get("bat_to_home_power")),
        _parse_signed(data.get("battery_to_grid_power")),
    )
    return charge, discharge


def _looks_like_export_not_battery_discharge(data: dict, discharge_w: int) -> bool:
    """Cloud bat_discharge_power often mirrors PV→grid when the pack is idle (not home load)."""
    if discharge_w <= 0:
        return False
    pv_grid = _parse_signed(data.get("photovoltaic_to_grid_power"))
    bat_grid = _parse_signed(data.get("battery_to_grid_power"))
    # output_power tracks AC to home while discharging — do not treat as grid export
    if not (
        (isinstance(pv_grid, int) and pv_grid > 0)
        or (isinstance(bat_grid, int) and bat_grid > 0)
    ):
        return False
    pv = _parse_signed(data.get("photovoltaic_power")) or _parse_signed(
        data.get("input_power")
    )
    if pv is None:
        pv = 0
    tol = max(150, int(pv * 0.08))
    if isinstance(pv_grid, int) and abs(discharge_w - pv_grid) <= tol:
        return True
    if isinstance(bat_grid, int) and abs(discharge_w - bat_grid) <= tol:
        return True
    return False


def _pick_from_api_totals(data: dict) -> tuple[int, int]:
    """Charge/discharge from bat_* / charging_power (cloud scene API)."""
    charge_api = _max_positive(
        _parse_signed(data.get("bat_charge_power")),
        _parse_signed(data.get("battery_charge_power")),
    )
    discharge_api = _max_positive(
        _parse_signed(data.get("bat_discharge_power")),
        _parse_signed(data.get("battery_discharge_power")),
    )

    signed = _parse_signed(data.get("charging_power"))
    if signed is not None:
        if signed > 0:
            return max(charge_api, signed), 0
        if signed < 0:
            return 0, max(discharge_api, abs(signed))
        # signed == 0: ignore bat_discharge only when it mirrors grid export
        if charge_api > 0:
            return charge_api, 0
        if discharge_api > 0 and not _looks_like_export_not_battery_discharge(
            data, discharge_api
        ):
            return 0, discharge_api
        balance = _pick_from_output_balance(data)
        if balance[0] > 0 or balance[1] > 0:
            return balance
        return 0, 0

    if charge_api or discharge_api:
        if discharge_api > 0 and charge_api == 0 and _looks_like_export_not_battery_discharge(
            data, discharge_api
        ):
            return 0, 0
        return charge_api, discharge_api

    return _pick_from_output_balance(data)


def pick_bat_charge_discharge(data: dict) -> tuple[int, int]:
    """Return (charge_W, discharge_W) from cloud cache only (scene info / device API)."""
    charge, discharge = _pick_from_api_totals(data)

    if _has_battery_flow_fields(data):
        flow_charge, flow_discharge = _pick_from_power_flows(data)
        charge = max(charge, flow_charge)
        discharge = max(discharge, flow_discharge)

    if (
        discharge > 0
        and charge == 0
        and _looks_like_export_not_battery_discharge(data, discharge)
    ):
        return 0, 0

    return charge, discharge


def scene_bank_entry(api: Any | None, site_id: str, sn: str) -> dict:
    """Solarbank row from site solarbank_info (AnkerSolix2 / scene API)."""
    if not api or not site_id or not sn:
        return {}
    site = (api.sites or {}).get(site_id) or {}
    for item in ((site.get("solarbank_info") or {}).get("solarbank_list") or []):
        if isinstance(item, dict) and str(item.get("device_sn") or "") == sn:
            return dict(item)
    return {}


def merge_bank_cache(item: dict, api: Any | None, site_id: str = "") -> dict:
    """Merge scene list row with device API cache; ignore MQTT."""
    sn = str(item.get("device_sn") or "").strip()
    dev = (api.devices.get(sn) if api and sn else None) or {}
    if not isinstance(dev, dict):
        dev = {}
    merged: dict[str, Any] = {}
    for src in (dev, item):
        if not isinstance(src, dict):
            continue
        for key, val in src.items():
            if key in ("mqtt_data", "mqtt_overlay", "mqtt_supported"):
                continue
            if val is not None and val != "":
                merged[key] = val
    if not site_id:
        site_id = str(merged.get("site_id") or dev.get("site_id") or "")
    scene = scene_bank_entry(api, site_id, sn)
    for key, val in scene.items():
        if val is None or val == "":
            continue
        if key in _BATTERY_POWER_KEYS:
            merged[key] = _coalesce_power_field(key, merged.get(key), val)
        else:
            merged[key] = val
    if sn:
        merged["device_sn"] = sn
    return merged


def enrich_solarbank_scene(api: Any, sn: str, ctx_data: dict) -> dict:
    """Overlay latest scene-API solarbank fields onto device poll cache."""
    site_id = str(ctx_data.get("site_id") or "")
    scene = scene_bank_entry(api, site_id, sn)
    if not scene:
        return ctx_data
    out = dict(ctx_data)
    for key, val in scene.items():
        if val is None or val == "":
            continue
        if key in _BATTERY_POWER_KEYS:
            out[key] = _coalesce_power_field(key, out.get(key), val)
        else:
            out[key] = val
    return out


def _site_solarbank_sns(
    sb_list: list, api: Any | None, site_id: str
) -> list[str]:
    """Device SNs for site banks (scene list + device cache)."""
    seen: set[str] = set()
    for item in sb_list or []:
        if isinstance(item, dict):
            sn = str(item.get("device_sn") or "").strip()
            if sn:
                seen.add(sn)
    if api and site_id:
        for sn, dev in (api.devices or {}).items():
            if not isinstance(dev, dict):
                continue
            if str(dev.get("site_id") or "") != site_id:
                continue
            dtype = str(dev.get("type") or dev.get("device_type") or "").lower()
            if dtype in ("solarbank", "solarbank_pps"):
                seen.add(str(sn))
    return sorted(seen)


def sum_bank_charge_discharge(
    sb_list: list, api: Any | None, site_id: str = ""
) -> tuple[int, int]:
    """Sum charge/discharge W — same source path as per-solarbank sensors."""
    charge = 0
    discharge = 0
    if not api:
        for item in sb_list or []:
            if not isinstance(item, dict):
                continue
            c, d = pick_bat_charge_discharge(item)
            charge += c
            discharge += d
        return charge, discharge

    for sn in _site_solarbank_sns(sb_list, api, site_id):
        dev = (api.devices or {}).get(sn) or {}
        if not isinstance(dev, dict):
            continue
        ctx = dict(dev)
        if site_id:
            ctx["site_id"] = site_id
        data = enrich_solarbank_scene(api, sn, ctx)
        c, d = pick_bat_charge_discharge(data)
        charge += c
        discharge += d
    return charge, discharge
