"""Serializer for converting grouping specifications to/from compact string format.

This module provides serialization capabilities for converting grouping specifications
to and from compact tuple-based string representations, suitable for use in
URL query parameters and APIs.

Format
------
Grouping specifications are serialized as a tuple of two comma-separated strings:
1. First element: comma-separated list of grouping field names
2. Second element: comma-separated list of aggregations

Aggregation format:
- count: "agg_id:count"
- other functions: "agg_id:function:field"

Examples
--------
>>> from therismos.grouping import GroupSpec, Aggregation, AggregationFunction
>>> from therismos.grouping.serializer import Serializer
>>>
>>> # Basic serialization
>>> spec = GroupSpec(
...     group_by=["category", "region"],
...     aggregations=[
...         Aggregation("total", AggregationFunction.COUNT),
...         Aggregation("min_price", AggregationFunction.MIN, "price"),
...         Aggregation("avg_price", AggregationFunction.AVERAGE, "price"),
...     ],
... )
>>> serializer = Serializer()
>>> text = serializer.serialize(spec)
>>> # Result: '("category,region", "total:count,min_price:min:price,avg_price:average:price")'
>>>
>>> # Deserialization
>>> spec = serializer.deserialize('("status", "count:count,avg:average:age")')
>>> # Result: GroupSpec with group_by=["status"], aggregations={...}
"""

from __future__ import annotations

import re

from therismos.grouping._grouping import Aggregation, AggregationFunction, GroupSpec


class Serializer:
    """Serializer for converting grouping specifications to/from string format.

    Example:
        >>> from therismos.grouping import GroupSpec, Aggregation, AggregationFunction
        >>> from therismos.grouping.serializer import Serializer
        >>> serializer = Serializer()
        >>> spec = GroupSpec(
        ...     group_by=["category"],
        ...     aggregations=[Aggregation("total", AggregationFunction.COUNT)],
        ... )
        >>> text = serializer.serialize(spec)
        >>> # Result: '("category", "total:count")'
    """

    def serialize(self, spec: GroupSpec, url_encode: bool = False) -> str:
        """Serialize a grouping specification to string format.

        :param spec: The grouping specification to serialize.
        :type spec: GroupSpec
        :param url_encode: Whether to URL-encode the result (currently not implemented).
        :type url_encode: bool
        :returns: Serialized string representation in tuple format.
        :rtype: str

        Example:
            >>> spec = GroupSpec(
            ...     group_by=["category", "region"],
            ...     aggregations=[
            ...         Aggregation("total", AggregationFunction.COUNT),
            ...         Aggregation("min_price", AggregationFunction.MIN, "price"),
            ...     ],
            ... )
            >>> serializer.serialize(spec)
            '("category,region", "total:count,min_price:min:price")'
        """
        # Serialize grouping fields
        group_by_str = ",".join(spec.group_by)

        # Serialize aggregations
        agg_parts = []
        for agg in spec.aggregations.values():
            agg_str = str(agg)  # Uses Aggregation.__str__
            agg_parts.append(agg_str)
        agg_str = ",".join(agg_parts)

        # Construct tuple format
        result = f'("{group_by_str}", "{agg_str}")'

        # URL encoding support (for future implementation)
        if url_encode:
            # Would use urllib.parse.quote here
            msg = "URL encoding is not yet implemented"
            raise NotImplementedError(msg)

        return result

    def deserialize(self, text: str) -> GroupSpec:
        """Deserialize a string to a grouping specification.

        :param text: The serialized grouping specification string.
        :type text: str
        :returns: The deserialized grouping specification.
        :rtype: GroupSpec
        :raises ValueError: If the string format is invalid.

        Example:
            >>> serializer.deserialize('("category,region", "total:count,avg:average:price")')
            GroupSpec(...)  # group_by=["category", "region"], aggregations={...}
        """
        # Match the tuple format: ("...", "...")
        match = re.match(r'^\(\s*"([^"]*)"\s*,\s*"([^"]*)"\s*\)$', text)

        if not match:
            msg = f"Invalid grouping specification format: {text}"
            raise ValueError(msg)

        group_by_str = match.group(1)
        agg_str = match.group(2)

        # Parse grouping fields
        if group_by_str.strip():
            group_by = [field.strip() for field in group_by_str.split(",")]
            # Filter out empty strings
            group_by = [field for field in group_by if field]
        else:
            group_by = []

        # Parse aggregations
        aggregations = []
        if agg_str.strip():
            agg_parts = agg_str.split(",")
            for agg_part in agg_parts:
                agg_part = agg_part.strip()
                if not agg_part:
                    continue

                # Parse aggregation: "id:function" or "id:function:field"
                components = agg_part.split(":")

                if len(components) == 2:
                    # Format: "id:count"
                    agg_id, func_str = components
                    field_name = None
                elif len(components) == 3:
                    # Format: "id:function:field"
                    agg_id, func_str, field_name = components
                else:
                    msg = f"Invalid aggregation format: {agg_part}"
                    raise ValueError(msg)

                # Parse aggregation function
                try:
                    func = AggregationFunction(func_str)
                except ValueError as e:
                    msg = f"Unknown aggregation function: {func_str}"
                    raise ValueError(msg) from e

                # Validate field requirement
                # Allow COUNT to have a field when deserializing, but ignore it
                if func == AggregationFunction.COUNT:
                    field_name = None
                elif field_name is None:
                    msg = f"{func.value} aggregation requires a field: {agg_part}"
                    raise ValueError(msg)

                # Create aggregation
                agg = Aggregation(agg_id, func, field_name)
                aggregations.append(agg)

        # Create and return GroupSpec
        return GroupSpec(group_by=group_by, aggregations=aggregations)


__all__ = [
    "Serializer",
]
