"""Parse FHIR JSON/dict to fhir.resources models with robust error handling."""

from __future__ import annotations

import json
from typing import Any, TypeVar

from pydantic import ValidationError

T = TypeVar("T")


class FhirExtractError(Exception):
    """Raised when extraction fails and raise_on_error=True."""

    def __init__(self, error: dict[str, Any]) -> None:
        self.error = error
        super().__init__(error.get("message", str(error)))


def _error(error_type: str, message: str, details: Any = None) -> dict[str, Any]:
    """Build structured error dict."""
    return {
        "success": False,
        "error": {"type": error_type, "message": message, "details": details},
    }


def _get_resource_class(resource_type: str):
    """Get fhir.resources R4B model class for resource type."""
    # Convert PascalCase to module name (e.g., MedicationRequest -> medicationrequest)
    mod_name = "".join(c for c in resource_type if c.isalnum()).lower()
    try:
        mod = __import__(f"fhir.resources.R4B.{mod_name}", fromlist=[resource_type])
        return getattr(mod, resource_type)
    except (ImportError, AttributeError) as e:
        raise ValueError(f"Unknown FHIR resource type: {resource_type}") from e


def _parse_resource(
    input_data: str | dict | Any,
    expected_resource_type: str,
) -> Any | dict[str, Any]:
    """Parse input to FHIR resource model. Returns model or error dict.

    input_data: JSON string, dict, or already-parsed fhir.resources model.
    expected_resource_type: e.g. 'Patient', 'Bundle', 'Observation'.
    """
    # Already a model - check type
    if hasattr(input_data, "resource_type"):
        rt = getattr(input_data, "resourceType", None) or getattr(
            input_data, "resource_type", None
        )
        if rt != expected_resource_type:
            return _error(
                "wrong_resource_type",
                f"Expected {expected_resource_type}, got {rt}",
            )
        return input_data

    # Empty input
    if input_data is None or (isinstance(input_data, str) and input_data.strip() == ""):
        return _error("empty_input", "No input provided")

    # Dict input - check resourceType first
    if isinstance(input_data, dict):
        rt = input_data.get("resourceType")
        if rt and rt != expected_resource_type:
            return _error(
                "wrong_resource_type",
                f"Expected {expected_resource_type}, got {rt}",
            )
        data = input_data
    else:
        # String - parse JSON first
        if isinstance(input_data, (bytes, bytearray)):
            input_data = input_data.decode("utf-8")
        try:
            data = json.loads(input_data)
        except json.JSONDecodeError as e:
            return _error("parse_error", "Invalid JSON", details=str(e))

        if not isinstance(data, dict):
            return _error("parse_error", "Expected JSON object", details=None)

        rt = data.get("resourceType")
        if rt and rt != expected_resource_type:
            return _error(
                "wrong_resource_type",
                f"Expected {expected_resource_type}, got {rt}",
            )

    # Parse with fhir.resources
    try:
        cls = _get_resource_class(expected_resource_type)
        return cls.model_validate(data)
    except ValidationError as e:
        errors = e.errors() if hasattr(e, "errors") else str(e)
        return _error(
            "validation_error",
            f"Invalid {expected_resource_type} resource",
            details=errors,
        )
    except ValueError as e:
        return _error("validation_error", str(e), details=None)
