from __future__ import annotations

from functools import wraps
from typing import Any, Callable

from jsonpath_ng import parse as jsonpath_parse

from ..validation.json_validator import JSONValidator


class JSONPathParser:
    """Decorators for extracting structured data from JSON responses."""

    @staticmethod
    def parse_field(json_path: str, field_name: str) -> Callable:
        def decorator(func: Callable[..., Any]) -> Callable[..., Validator]:
            @wraps(func)
            async def wrapper(*args, **kwargs) -> Validator:
                validated = as_validator(await func(*args, **kwargs))

                content_type = (validated.headers.get("content-type", "") or "").lower()
                is_json = "application/json" in content_type

                try:
                    data = validated.json()
                    data = JSONValidator.normalize(data)
                except Exception as exc:
                    validated.valid.PARSED[field_name] = None
                    validated.valid.add_error(
                        key="JSON_DECODE",
                        expected=json_path,
                        actual=f"{type(exc).__name__}: {exc}",
                    )
                    if not is_json:
                        validated.valid.add_error(
                            key="CONTENT_TYPE",
                            expected="application/json",
                            actual=(content_type.split(";")[0] or "unknown"),
                        )
                    return validated
                if data is None:
                    validated.valid.PARSED[field_name] = None
                    validated.valid.add_error(
                        key="JSON_DECODE",
                        expected=json_path,
                        actual="invalid or empty JSON",
                    )
                    if not is_json:
                        validated.valid.add_error(
                            key="CONTENT_TYPE",
                            expected="application/json",
                            actual=(content_type.split(";")[0] or "unknown"),
                        )
                    return validated

                try:
                    expr = jsonpath_parse(json_path)
                    matches = expr.find(data)
                except Exception as exc:
                    validated.valid.PARSED[field_name] = None
                    validated.valid.add_error(
                        key="JSON_PATH",
                        expected=json_path,
                        actual=f"{type(exc).__name__}: {exc}",
                    )
                    return validated

                if not matches:
                    validated.valid.PARSED[field_name] = None
                    validated.valid.add_error(
                        key="JSON_PATH",
                        expected=field_name,
                        actual="not found",
                    )
                elif len(matches) == 1:
                    validated.valid.PARSED[field_name] = matches[0].value
                else:
                    validated.valid.PARSED[field_name] = [match.value for match in matches]

                return validated

            return wrapper

        return decorator
