from ..helpers import nativestr


def list_to_dict(aList):
    return {nativestr(aList[i][0]): nativestr(aList[i][1]) for i in range(len(aList))}


def _pairs_to_dict(pairs):
    """Convert a list of [key, value] pairs to a dict without forcing str."""
    return {pairs[i][0]: pairs[i][1] for i in range(len(pairs))}


def _nativestr_dict(d):
    """Apply ``nativestr`` to every key and string-typed value of ``d``.

    Used by the RESP3-to-RESP2-legacy adapters so labels coming from a
    RESP3 native map match today's RESP2 ``list_to_dict`` semantics.
    """
    return {
        nativestr(k): nativestr(v) if isinstance(v, (bytes, str)) else v
        for k, v in d.items()
    }


def parse_query_labels(response, **kwargs):
    """Normalize the TS.QUERYLABELS reply to a ``set`` of unique strings.

    The reply is a flat array under RESP2 and a set under RESP3; both are
    surfaced as a Python ``set`` so the collection type is identical across
    protocols and reflects that the server returns a unique, unordered
    collection. Elements are kept byte-exact (never coerced or normalized) and
    the server already deduplicates them. A missing reply maps to an empty
    ``set`` so an empty result is a normal success rather than an error.
    """
    if response is None:
        return set()
    return set(response)


def parse_range(response, **kwargs):
    """Parse range response. Used by TS.RANGE and TS.REVRANGE (legacy shape)."""
    if not response:
        return []
    # Multi-aggregator: samples have >2 elements [timestamp, val1, val2, ...]
    if len(response[0]) > 2:
        return [tuple([r[0]] + [float(v) for v in r[1:]]) for r in response]
    return [tuple((r[0], float(r[1]))) for r in response]


def parse_range_unified(response, **kwargs):
    """Unified parser for TS.RANGE / TS.REVRANGE.

    Returns ``list[list]`` rather than ``list[tuple]`` so the unified
    shape is symmetric with the RESP3 wire format.
    """
    if not response:
        return []
    if len(response[0]) > 2:
        return [[r[0]] + [float(v) for v in r[1:]] for r in response]
    return [[r[0], float(r[1])] for r in response]


def parse_n_range(response, **kwargs):
    """Parse the TS.NRANGE / TS.NREVRANGE response.

    The wire shape is ``[[timestamp, [value_0, value_1, ...]], ...]`` where the
    value array preserves input key order. Its length equals the number of
    queried keys, except when a per-key AGGREGATION spec lists multiple
    aggregators: each such key then contributes one value per aggregator (in
    spec order), concatenated in key order. Rows are returned in server order
    (never re-sorted or reversed), and a missing raw sample or missing
    aggregation bucket is kept as ``float('nan')`` rather than converted to
    ``None``.
    """
    if not response:
        return []
    return [[int(row[0]), [float(v) for v in row[1]]] for row in response]


def parse_get(response):
    """Parse get response. Used by TS.GET (legacy shape)."""
    if not response:
        return None
    return int(response[0]), float(response[1])


def parse_get_unified(response, **kwargs):
    """Unified parser for TS.GET. Returns ``[int, float]``."""
    if not response:
        return None
    return [int(response[0]), float(response[1])]


def parse_m_get(response):
    """Parse multi get response (RESP2 wire)."""
    res = []
    for item in response:
        if not item[2]:
            res.append({nativestr(item[0]): [list_to_dict(item[1]), None, None]})
        else:
            res.append(
                {
                    nativestr(item[0]): [
                        list_to_dict(item[1]),
                        int(item[2][0]),
                        float(item[2][1]),
                    ]
                }
            )
    return sorted(res, key=lambda d: list(d.keys()))


def parse_m_get_unified(response, **kwargs):
    """Unified parser for TS.MGET.

    Emits ``{key: [labels_dict, sample]}`` where ``sample`` is
    ``[int, float]`` or ``[]`` when no sample exists. Handles both wire
    formats: the RESP2 wire arrives as a list of ``[key, label_pairs,
    sample]`` triples; the RESP3 wire is already keyed by name.
    """
    if isinstance(response, dict):
        res = {}
        for key, item in response.items():
            sample = item[1] if len(item) > 1 else []
            if not sample:
                res[key] = [item[0], []]
            else:
                res[key] = [item[0], [int(sample[0]), float(sample[1])]]
        return res
    res = {}
    for item in response:
        if not item[2]:
            res[item[0]] = [_pairs_to_dict(item[1]), []]
        else:
            res[item[0]] = [
                _pairs_to_dict(item[1]),
                [int(item[2][0]), float(item[2][1])],
            ]
    return res


def parse_m_get_resp3_to_resp2_legacy(response, **kwargs):
    """RESP3 wire → today's RESP2 legacy shape for TS.MGET."""
    res = []
    for key, item in response.items():
        labels = _nativestr_dict(item[0]) if item[0] else {}
        sample = item[1] if len(item) > 1 else []
        if not sample:
            res.append({nativestr(key): [labels, None, None]})
        else:
            res.append({nativestr(key): [labels, int(sample[0]), float(sample[1])]})
    return sorted(res, key=lambda d: list(d.keys()))


def parse_m_range(response, **kwargs):
    """Parse multi range response (RESP2 wire)."""
    res = []
    for item in response:
        res.append({nativestr(item[0]): [list_to_dict(item[1]), parse_range(item[2])]})
    return sorted(res, key=lambda d: list(d.keys()))


def _m_range_metadata(aggregation_type=None):
    if aggregation_type is None:
        # Aggregators are empty when TS.MRANGE/TS.MREVRANGE is called without
        # AGGREGATION; this mirrors RESP3 metadata such as {"aggregators": []}.
        return {"aggregators": []}
    if isinstance(aggregation_type, list):
        aggregators = aggregation_type
    else:
        aggregators = [aggregation_type]
    return {"aggregators": [nativestr(agg).lower() for agg in aggregators]}


def parse_m_range_unified(response, **kwargs):
    """Unified parser for TS.MRANGE / TS.MREVRANGE.

    Emits ``{key: [labels_dict, metadata, samples]}`` regardless of wire
    format. RESP2 has no metadata element on the wire, so the command options
    are used to synthesize the same ``{"aggregators": ...}`` structure that
    RESP3 returns.
    """
    if isinstance(response, dict):
        res = {}
        for key, item in response.items():
            metadata = item[1] if len(item) > 2 else []
            res[key] = [item[0], metadata, parse_range_unified(item[-1])]
        return res
    res = {}
    metadata = _m_range_metadata(kwargs.get("aggregation_type"))
    for item in response:
        res[item[0]] = [
            _pairs_to_dict(item[1]),
            metadata,
            parse_range_unified(item[2]),
        ]
    return res


def parse_m_range_resp3_to_resp2_legacy(response, **kwargs):
    """RESP3 wire → today's RESP2 legacy shape for TS.MRANGE / TS.MREVRANGE."""
    res = []
    for key, item in response.items():
        labels = _nativestr_dict(item[0]) if item[0] else {}
        samples = item[-1]
        res.append({nativestr(key): [labels, parse_range(samples)]})
    return sorted(res, key=lambda d: list(d.keys()))
