"""JSON dict list storage backend.

Stores OHRs as a list of nested dicts — the "orientation model" from the
architecture spec.  Queries operate on the dict structures directly.
"""

from __future__ import annotations

import logging
from typing import Any

from flexohr.core.ohr import OHR

logger = logging.getLogger(__name__)


class JsonDictBackend:
    """Store OHRs as a list of nested dicts.

    This is the reference storage model: each OHR is its own
    self-describing dict produced by ``OHR.to_dict()``.
    """

    def __init__(self) -> None:
        self._dicts: list[dict[str, Any]] = []
        self._ohrs: list[OHR] = []

    # ── Ingest ──

    def add_ohr(self, ohr: OHR) -> int:
        """Add an OHR, returning its index."""
        idx = len(self._ohrs)
        self._ohrs.append(ohr)
        self._dicts.append(ohr.to_dict())
        return idx

    def add_ohrs(self, ohrs: list[OHR]) -> list[int]:
        """Add multiple OHRs."""
        return [self.add_ohr(ohr) for ohr in ohrs]

    # ── Access ──

    def get_dict(self, idx: int) -> dict[str, Any]:
        return self._dicts[idx]

    def get_ohr(self, idx: int) -> OHR:
        return self._ohrs[idx]

    def reconstruct_ohr(self, idx: int) -> OHR:
        """Reconstruct OHR from stored dict (tests round-trip)."""
        return OHR.from_dict(self._dicts[idx])

    def reconstruct_all(self) -> list[OHR]:
        """Reconstruct all OHRs from dicts."""
        return [OHR.from_dict(d) for d in self._dicts]

    # ── Queries ──

    def query_ohrs_by_property(self, key: str, value: Any) -> list[int]:
        """Find OHR indices whose top-level properties match."""
        results = []
        for i, d in enumerate(self._dicts):
            for prop in d.get("properties", []):
                if prop.get("type") == "enum" and prop.get("value") == value:
                    results.append(i)
                    break
                if prop.get("type") == "key_value" and prop.get("key") == key and prop.get("value") == value:
                    results.append(i)
                    break
        return results

    def query_components_by_property(self, key: str, value: Any) -> list[dict]:
        """Find all component dicts (across all OHRs) matching a property."""
        results = []
        for d in self._dicts:
            results.extend(self._find_components_in_dict(d, key, value))
        return results

    def query_all_components(self) -> list[dict]:
        """Return all component dicts across all OHRs (flattened)."""
        results = []
        for d in self._dicts:
            results.extend(self._extract_all_components(d))
        return results

    def _extract_all_components(self, d: dict) -> list[dict]:
        """Recursively extract all component dicts from an OHR dict."""
        comps = []
        if d.get("type") in ("component", "absent_component", "undefined_component"):
            comps.append(d)
            return comps
        # It's an OHR dict
        ref = d.get("reference_component")
        if ref is not None:
            comps.append(ref)
        for item in d.get("body", []):
            if item.get("type") == "ohr":
                comps.extend(self._extract_all_components(item))
            else:
                comps.append(item)
        ref_ohr = d.get("reference_ohr")
        if ref_ohr is not None:
            comps.extend(self._extract_all_components(ref_ohr))
        return comps

    def _find_components_in_dict(self, d: dict, key: str, value: Any) -> list[dict]:
        """Find components matching property key/value in an OHR dict tree."""
        results = []
        for comp in self._extract_all_components(d):
            for prop in comp.get("properties", []):
                if prop.get("type") == "enum":
                    # Match by enum class name (snake_cased) or value
                    from flexohr.harmony.enums import _camel_to_snake

                    enum_key = _camel_to_snake(prop.get("enum_class", ""))
                    if enum_key == key and prop.get("value") == value:
                        results.append(comp)
                        break
                elif prop.get("type") == "key_value":
                    if prop.get("key") == key and prop.get("value") == value:
                        results.append(comp)
                        break
        return results

    def count_components(self) -> int:
        """Total component count across all OHRs."""
        return sum(len(self._extract_all_components(d)) for d in self._dicts)

    def count_ohrs(self) -> int:
        """Number of top-level OHRs stored."""
        return len(self._dicts)

    @property
    def memory_bytes(self) -> int:
        """Approximate memory usage."""
        import sys

        return sum(sys.getsizeof(d) for d in self._dicts) + sys.getsizeof(self._ohrs)
