import logging
import warnings
from typing import Any
from typing import Dict
from typing import List
from typing import Optional
from typing import Tuple

from icat_esrf_definitions.errors import ValidationWarning
from icat_esrf_definitions.models import IcatDatasetParameters
from pydantic import ValidationError

from .... import errors

logger = logging.getLogger(__name__)


def pydantic_serialization(
    parameters: Dict[str, Any], strict: bool = False
) -> Tuple[Dict[str, Any], List[str], Optional[Exception]]:
    """
    Pydantic serialization of dataset parameters.

    :raises IcatMetadataValidationError: If ``strict=True`` and the metadata is invalid.
    :emits IcatMetadataValidationWarning: If ``strict=False`` and the metadata is invalid.
    :emits IcatMetadataDeprecatedWarning: If metadata is deprecated.
    """
    parsed_parameters = parameters
    unknown_keys = []
    error = None
    captured_warnings = []

    try:
        try:
            try:
                parsed_parameters, unknown_keys, captured_warnings = (
                    _fully_pydantic_serialization(parameters)
                )
            except ValidationError as ex:
                logger.debug(
                    "Full Pydantic serialization failed; attempting partial serialization",
                    exc_info=ex,
                )
                try:
                    parsed_parameters, unknown_keys = _partial_pydantic_serialization(
                        parameters, ex
                    )
                    logger.debug("Partial Pydantic serialization succeeded")
                except Exception as inner_ex:
                    logger.debug(
                        "Partial Pydantic serialization failed; fallback to native string serialization",
                        exc_info=inner_ex,
                    )
                raise
        finally:
            for warning in captured_warnings:
                message = str(warning.message)
                if isinstance(warning, DeprecationWarning):
                    errors.warn(message, errors.IcatMetadataDeprecatedWarning)
                elif isinstance(warning, ValidationWarning):
                    errors.invalid_warn_or_raise(message, strict=strict)
                else:
                    errors.warn(message, errors.IcatMetadataValidationWarning)

    except Exception as ex:
        error = ex

    return parsed_parameters, unknown_keys, error


def _fully_pydantic_serialization(
    parameters: Dict[str, Any],
) -> Tuple[Dict[str, Any], List[str], List[Warning]]:
    """
    Pydantic serialization of all dataset parameters.

    Capture the following warnings

    - Pydantic `DeprecationWarning`
    - icat-esrf-definitions `ValidationWarning`
    """
    with warnings.catch_warnings(record=True) as capture_warnings:
        warnings.simplefilter("always", DeprecationWarning)
        warnings.simplefilter("always", ValidationWarning)
        model, unknown_keys = IcatDatasetParameters.from_icat_dict_known_keys(
            parameters
        )
        parsed_parameters = model.to_icat_dict()
    return parsed_parameters, unknown_keys, capture_warnings


def _partial_pydantic_serialization(
    parameters: Dict[str, Any], error: ValidationError
) -> Tuple[Dict[str, Any], List[str]]:
    """
    Pydantic serialization of the dataset parameter that did not cause a validation error.

    Hide Pydantic `DeprecationWarning`.
    """
    invalid_names = IcatDatasetParameters.icat_field_names_with_validation_error(error)
    valid_parameters = {k: v for k, v in parameters.items() if k not in invalid_names}
    invalid_parameters = {k: v for k, v in parameters.items() if k in invalid_names}

    with warnings.catch_warnings():
        warnings.simplefilter("ignore", DeprecationWarning)
        model, unknown_keys = IcatDatasetParameters.from_icat_dict_known_keys(
            valid_parameters
        )
        parsed_valid_parameters = model.to_icat_dict()
        parsed_parameters = {**parsed_valid_parameters, **invalid_parameters}

    return parsed_parameters, unknown_keys
