"""Discovery upload via the session flow (Phase 2).

Drives open-session -> per-category items -> complete/failed against the
Central API. Upload is best-effort: any transport failure is logged and the
local last.json snapshot remains the fallback. A category whose upload fails is
reported in failed_categories so the backend records the session as partial
(never silently dropped, never reconciled away).
"""

from __future__ import annotations

from typing import Any

from api_client import ApiClient, ApiError
from config import Config
from identity import load_agent_token
from logger import get_logger

log = get_logger("discovery.upload")


def _coerce_items(value: Any) -> list[dict[str, Any]]:
    """Normalize a category value into a list of dict items for raw storage."""
    if isinstance(value, list):
        return [item if isinstance(item, dict) else {"value": item} for item in value]
    if isinstance(value, dict):
        return [value]
    return [{"value": value}]


def categories_from_inventory(inventory: dict[str, Any]) -> dict[str, list[dict[str, Any]]]:
    """Map a full-discovery inventory dict to category -> list-of-items."""
    return {category: _coerce_items(value) for category, value in inventory.items()}


def upload_inventory(
    config: Config,
    api_client: ApiClient,
    scan_type: str,
    categories: dict[str, list[dict[str, Any]]],
    token: str | None = None,
) -> dict[str, Any] | None:
    """Run the full session flow for one scan. Returns the finalize response.

    Never raises: returns None if the session could not even be opened.
    """
    token = token or load_agent_token(config)
    expected = sorted(categories.keys())

    try:
        opened = api_client.open_discovery_session(
            {"scan_type": scan_type, "expected_categories": expected}, token
        )
        session_id = opened["session_id"]
    except (ApiError, KeyError, TypeError) as exc:
        log.warning("discovery session open failed: %s", exc)
        return None

    succeeded: list[str] = []
    failed: list[str] = []
    for category, items in categories.items():
        try:
            api_client.upload_discovery_items(
                {"session_id": session_id, "category": category, "items": items},
                token,
            )
            succeeded.append(category)
        except ApiError as exc:
            log.warning("discovery items upload failed for %s: %s", category, exc)
            failed.append(category)

    try:
        if not succeeded:
            # Nothing landed: fail the session so the backend does not reconcile.
            result = api_client.fail_discovery(
                {"session_id": session_id, "error": "all category uploads failed"},
                token,
            )
        else:
            result = api_client.complete_discovery(
                {
                    "session_id": session_id,
                    "succeeded_categories": succeeded,
                    "failed_categories": failed,
                },
                token,
            )
        log.info(
            "discovery %s scan finalized: session=%s status=%s",
            scan_type,
            session_id,
            result.get("status"),
        )
        return result
    except ApiError as exc:
        log.warning("discovery finalize failed: %s", exc)
        return None
