# logic.py — ТЕСТОВАЯ ВЕРСИЯ 0.0.2
# ============================================
# Не управляет светом. Только:
# - публикует память (GC + IDF heap)
# - публикует конфиг net
# - публикует счётчики boot_count / poweron_count
# - принимает команды: status, mem, config, reset
# - reset → machine.reset() для проверки HARD_RESET
# ============================================

import asyncio
import gc
import json
import machine
import os
import time
import net

VERSION = "test-0.0.2"

STATUS_INTERVAL_SEC = 60


def _ts():
    tm = time.localtime()
    return f"{tm[3]:02d}:{tm[4]:02d}:{tm[5]:02d}"


async def _publish(topic, payload, qos=1):
    if not (net.wifi_ok and net.net_ok and net.time_ok and net.mqtt_ok):
        return False
    try:
        if isinstance(payload, dict):
            payload = json.dumps(payload)
        return await net.publish(topic, payload, qos=qos)
    except Exception as e:
        print(f"[{_ts()}] [logic] publish error:", type(e).__name__, e)
        return False


def _read_gc_info():
    try:
        return {
            "free": gc.mem_free(),
            "alloc": gc.mem_alloc(),
            "threshold": gc.threshold(),
        }
    except Exception as e:
        return {"error": f"{type(e).__name__}: {e}"}


def _read_heap_info():
    try:
        import esp32

        blocks = esp32.idf_heap_info(esp32.HEAP_DATA)
        result = []
        for block in blocks:
            size, free, alloc, largest = block
            frag_pct = (largest * 100 // free) if free > 0 else 0
            result.append(
                {
                    "size": size,
                    "free": free,
                    "alloc": alloc,
                    "largest_free": largest,
                    "frag_pct": frag_pct,
                }
            )
        return result
    except AttributeError as e:
        return {"error": f"AttributeError: {e}"}
    except Exception as e:
        return {"error": f"{type(e).__name__}: {e}"}


def _read_boot_counters():
    try:
        with open("/boot_count.json") as f:
            data = json.load(f)
            return data.get("count", 0), data.get("poweron_count", 0)
    except Exception:
        return 0, 0


def _collect_status():
    """Собирает всё для публикации."""
    gc.collect()
    bc, pc = _read_boot_counters()

    return {
        "version": VERSION,
        "uptime": time.ticks_diff(time.ticks_ms(), _start_ticks) // 1000,
        "ts": int(time.time()) + 946684800 if time.time() > 100000 else None,
        "time_ok": net.time_ok,
        "wifi_ok": net.wifi_ok,
        "net_ok": net.net_ok,
        "time_ok_flag": net.time_ok,
        "mqtt_ok": net.mqtt_ok,
        "all_ok": net.all_ok,
        "safe_mode": net.safe_mode,
        "boot_count": bc,
        "poweron_count": pc,
        "gc": _read_gc_info(),
        "heap": _read_heap_info(),
        "net_config": dict(net.config),
    }


async def _publish_status(reason):
    data = _collect_status()
    data["reason"] = reason
    await _publish("data/test/status", data)
    print(f"[{_ts()}] [logic] status published ({reason})")


async def _publish_mem():
    gc.collect()
    data = {
        "gc": _read_gc_info(),
        "heap": _read_heap_info(),
        "uptime": time.ticks_diff(time.ticks_ms(), _start_ticks) // 1000,
        "ts": int(time.time()) + 946684800 if time.time() > 100000 else None,
    }
    await _publish("data/test/mem", data)


async def on_command(payload):
    print(f"[{_ts()}] [logic] command:", payload)

    try:
        data = json.loads(payload)
    except Exception:
        return

    if not isinstance(data, dict):
        return

    action = data.get("action")

    if action == "status":
        await _publish_status("command")

    elif action == "mem":
        await _publish_mem()

    elif action == "config":
        await _publish("data/test/net_config", dict(net.config))
        print(f"[{_ts()}] [logic] net_config published")

    elif action == "reset":
        print(f"[{_ts()}] [logic] RESET command received")
        await _publish(
            "data/test/status",
            {
                "reason": "before_reset",
                "uptime": time.ticks_diff(time.ticks_ms(), _start_ticks) // 1000,
                "ts": int(time.time()) + 946684800 if time.time() > 100000 else None,
            },
        )
        await asyncio.sleep(1)
        print(f"[{_ts()}] [logic] machine.reset()")
        machine.reset()


async def on_data(topic, payload):
    pass


async def run():
    global _start_ticks
    _start_ticks = time.ticks_ms()

    print(f"[{_ts()}] [logic] {VERSION} ЗАПУЩЕНА")

    net.on_command(on_command)
    net.on_data(on_data)

    # Ждём, пока net поднимется
    await asyncio.sleep(5)

    # Публикуем статус при старте
    try:
        await _publish_status("startup")
    except Exception as e:
        print(f"[{_ts()}] [logic] startup publish error:", type(e).__name__, e)

    last_status = time.ticks_ms()

    while True:
        if net.ota_pending:
            print(f"[{_ts()}] [logic] OTA pending")
            net.set_ota_safe(True)
            while net.ota_pending:
                await asyncio.sleep(1)
            net.set_ota_safe(False)

        # Периодический статус раз в STATUS_INTERVAL_SEC
        now_ms = time.ticks_ms()
        if time.ticks_diff(now_ms, last_status) >= STATUS_INTERVAL_SEC * 1000:
            try:
                await _publish_status("periodic")
            except Exception as e:
                print(
                    f"[{_ts()}] [logic] periodic publish error:",
                    type(e).__name__,
                    e,
                )
            last_status = now_ms

        await asyncio.sleep(1)
