"""Firmware image discovery and bulk upgrade job submission."""

from __future__ import annotations

import re
from collections import defaultdict
from dataclasses import dataclass
from typing import Any

from .client import CnMaestroClient
from .devices import Device

JOB_BATCH_SIZE = 100


@dataclass(frozen=True)
class FirmwareImage:
    product: str
    version: str
    name: str  # package name to send in job payload
    raw: dict[str, Any]


def _parse_version(v: str) -> tuple[int, ...]:
    """Loose semver-ish parser. Non-numeric parts sort lower than numeric."""
    parts = re.split(r"[._\-+]", v)
    out: list[int] = []
    for p in parts:
        m = re.match(r"^(\d+)", p)
        if m:
            out.append(int(m.group(1)))
        else:
            out.append(-1)
    return tuple(out) if out else (-1,)


def list_firmware(client: CnMaestroClient) -> list[FirmwareImage]:
    body = client.get("/api/v2/firmware")
    items = body.get("data", []) if isinstance(body, dict) else body or []
    images: list[FirmwareImage] = []
    for f in items:
        product = f.get("product") or f.get("product_type") or "unknown"
        version = f.get("version") or f.get("software_version") or ""
        name = f.get("package") or f.get("name") or version
        if not version:
            continue
        images.append(
            FirmwareImage(product=product, version=version, name=name, raw=f)
        )
    return images


def latest_per_product(images: list[FirmwareImage]) -> dict[str, FirmwareImage]:
    """Return the highest-version image per product."""
    best: dict[str, FirmwareImage] = {}
    for img in images:
        cur = best.get(img.product)
        if cur is None or _parse_version(img.version) > _parse_version(cur.version):
            best[img.product] = img
    return best


@dataclass
class UpgradeGroup:
    product: str
    target: FirmwareImage
    devices: list[Device]


def plan_upgrades(
    devices: list[Device],
    images: list[FirmwareImage],
    products_filter: set[str] | None = None,
) -> list[UpgradeGroup]:
    """Build upgrade groups for online devices not already on the latest."""
    targets = latest_per_product(images)
    by_product: dict[str, list[Device]] = defaultdict(list)
    for d in devices:
        if not d.is_online:
            continue
        if products_filter and d.product not in products_filter:
            continue
        target = targets.get(d.product)
        if target is None:
            continue
        if d.software_version and _parse_version(d.software_version) >= _parse_version(
            target.version
        ):
            continue
        by_product[d.product].append(d)
    groups: list[UpgradeGroup] = []
    for product, devs in by_product.items():
        groups.append(
            UpgradeGroup(product=product, target=targets[product], devices=devs)
        )
    return groups


def chunked(seq: list[Any], size: int) -> list[list[Any]]:
    return [seq[i : i + size] for i in range(0, len(seq), size)]


def submit_upgrade(
    client: CnMaestroClient, group: UpgradeGroup
) -> list[dict[str, Any]]:
    """Submit one or more firmware jobs for the group (chunked)."""
    results: list[dict[str, Any]] = []
    for batch in chunked([d.mac for d in group.devices], JOB_BATCH_SIZE):
        payload = {"devices": batch, "package": group.target.name}
        resp = client.post("/api/v2/devices/jobs/firmware", payload) or {}
        results.append(resp)
    return results
