"""
Utilities for wrapping and converting errors.

Converts Pydantic ValidationErrors into user-friendly config errors with context.
"""

from __future__ import annotations
import re
from typing import TYPE_CHECKING, NoReturn
from pathlib import Path

# Import Pydantic for runtime use
from pydantic import ValidationError as PydanticValidationError

if TYPE_CHECKING:
    from .context import ConfigErrorContext

from .context import ConfigErrorContext, ConfigSourceType
from .exceptions import (
    ConfigError,
    ConfigValidationError,
    ConfigFieldMissingError,
    ConfigFieldTypeError,
)
from .formatters import generate_field_suggestion


def wrap_pydantic_error(
    pydantic_error: PydanticValidationError,
    context: ConfigErrorContext
) -> ConfigValidationError:
    """
    Wrap Pydantic ValidationError with rich context.

    Converts raw Pydantic errors into user-friendly ConfigValidationError.
    Detects specific error types (missing field, type error) and creates
    specialized error classes when appropriate.

    Args:
        pydantic_error: Original Pydantic ValidationError
        context: Config context (file, profile, connection)

    Returns:
        ConfigValidationError or specialized subclass
    """
    errors = pydantic_error.errors()

    # Check if it's a simple missing field error
    if len(errors) == 1 and errors[0]["type"] == "missing":
        # Convert loc to list of strings (Pydantic loc can have ints for list indices)
        loc = [str(part) for part in errors[0]["loc"]]
        suggestion = _generate_missing_field_suggestion(loc, context)
        return ConfigFieldMissingError(
            field_path=loc,
            context=context,
            suggestion=suggestion
        )

    # Check if it's a type error
    if len(errors) == 1 and errors[0]["type"].startswith("type_error"):
        # Convert loc to list of strings (Pydantic loc can have ints for list indices)
        loc = [str(part) for part in errors[0]["loc"]]
        expected_type = errors[0].get("type", "unknown")
        actual_value = errors[0].get("input")
        return ConfigFieldTypeError(
            field_path=loc,
            expected_type=expected_type,
            actual_value=actual_value,
            context=context
        )

    # Collapse multiple literal_error branches for the same field into one clean message.
    # This happens when EngineSize is a union of Literal types — Pydantic reports one error
    # per branch (with the branch name appended to loc) instead of a single error.
    if errors and all(e["type"] == "literal_error" for e in errors):
        # loc[-1] is the branch discriminator (e.g. "literal['XS','S','M','L']") — strip it
        base_locs = [tuple(e["loc"][:-1]) for e in errors]
        if len(set(base_locs)) == 1:
            loc = [str(part) for part in errors[0]["loc"][:-1]]
            # Collect all valid values across branches, preserving order, deduplicating
            # ctx["expected"] format: "'XS', 'S', 'M' or 'L'" — split on commas and " or "
            seen: set[str] = set()
            valid_values: list[str] = []
            for e in errors:
                for v in re.split(r",\s*|\s+or\s+", e.get("ctx", {}).get("expected", "")):
                    v = v.strip().strip("'")
                    if v and v not in seen:
                        seen.add(v)
                        valid_values.append(v)
            actual_value = errors[0].get("input", "")
            summary = (
                f"Invalid value '{actual_value}' for '{' → '.join(loc)}'. "
                f"Valid values: {', '.join(valid_values)}"
            )
            return ConfigValidationError(
                message=summary,
                context=context,
                original_error=pydantic_error,
                pydantic_errors=errors
            )

    # Generic validation error with multiple issues
    summary = f"{len(errors)} validation error(s)"
    return ConfigValidationError(
        message=summary,
        context=context,
        original_error=pydantic_error,
        pydantic_errors=errors
    )


def _generate_missing_field_suggestion(
    field_path: list[str],
    context: ConfigErrorContext
) -> str:
    """
    Generate suggestion for missing field based on field name.

    Provides sensible default example values for common field names.

    Args:
        field_path: Path to missing field
        context: Config context

    Returns:
        Suggestion string with config snippet
    """
    # Provide sensible defaults based on field name
    field_name = field_path[-1] if field_path else ""

    # Common field example values
    example_values = {
        "account": "your_account_id",
        "user": "your_username",
        "password": "your_password",
        "warehouse": "your_warehouse",
        "passcode": "123456",
        "token": "your_token",
        "private_key_path": "/path/to/rsa_key.pem",
        "database": "your_database",
        "schema": "your_schema",
        "engine": "your_engine_name",
        "role": "your_role",
        "host": "localhost",
        "port": "8080",
    }

    example = example_values.get(field_name, "your_value_here")

    return generate_field_suggestion(field_path, context, example)


def print_and_raise(error: ConfigError) -> NoReturn:
    """
    Print Rich formatted error and raise with clean stack trace.

    Use this when you already have a constructed ConfigError.
    """
    import sys

    if sys.stdout.isatty():
        print(error.format_rich(), file=sys.stderr)

    raise error from None


def raise_config_error(
    exception: Exception,
    context: ConfigErrorContext,
    message: str | None = None
) -> NoReturn:
    """
    Wrap and raise exception as ConfigValidationError with clean stack trace.

    Handles both Pydantic and non-Pydantic errors.
    Prints Rich formatted error before raising (if in TTY).
    """
    if isinstance(exception, PydanticValidationError):
        error = wrap_pydantic_error(exception, context)
    else:
        error = ConfigValidationError(
            message=message or f"Failed to load config: {str(exception)}",
            context=context,
            original_error=exception
        )

    print_and_raise(error)


def create_context_from_config_class(
    config_class_name: str,
    file_path: Path | None = None
) -> ConfigErrorContext:
    """
    Create ConfigErrorContext from config class name.

    Maps config class names to source types:
    - RAIConfig -> "rai"
    - ConfigFromSnowflake -> "snowflake"
    - ConfigFromDBT -> "dbt"
    - ConfigFromRAIConfigToml -> "raiconfig_toml"

    Args:
        config_class_name: Name of the config class (e.g., "RAIConfig")
        file_path: Optional path to config file

    Returns:
        ConfigErrorContext with appropriate source type
    """
    # Map config class names to source types
    source_map = {
        "RAIConfig": ConfigSourceType.RAI,
        "ConfigFromSnowflake": ConfigSourceType.SNOWFLAKE,
        "ConfigFromDBT": ConfigSourceType.DBT,
        "ConfigFromRAIConfigToml": ConfigSourceType.RAICONFIG_TOML,
    }

    source_type = source_map.get(config_class_name, ConfigSourceType.PROGRAMMATIC)

    return ConfigErrorContext(
        source_type=source_type,
        file_path=file_path
    )
