"""
TEMPORARY: Translator from Config v1 to Config v0 format.

This module provides compatibility with v0 executors during migration.
DELETE THIS FILE when all v0 executors are migrated to use v1 config.

NOTE: This is a temporary shim to support legacy v0 executors.
      Do not add new features here - migrate executors to v1 config instead.

Architecture:
    Config v1 (Pydantic) → Translator → Config v0 (dict)

Usage:
    >>> from relationalai.config import create_config
    >>> from relationalai.config.v1_to_v0_translator import translate_v1_to_v0
    >>>
    >>> v1_config = create_config()
    >>> v0_dict = translate_v1_to_v0(v1_config)

Key Functions:
    - translate_v1_to_v0: Main entry point
    - _reverse_field_mappings: Create v1→v0 path mapping
    - _get_nested_value: Navigate nested attributes
    - _extract_connection_fields: Flatten connection to top-level
    - _apply_v0_defaults: Apply v0 config defaults
"""

import importlib

from typing import Any, Dict, Literal, cast

from .config import Config
from .connections import ConnectionConfig
from .external.raiconfig_converter import FIELD_MAPPINGS
from .config_reasoners_fields import LogicReasonerConfig
from .direct_access import validate_direct_access_auth


def _get_reasoner_field_mappings() -> Dict[str, str]:
    """
    Dynamically generate reasoner field mappings from Pydantic models.

    This introspects the LogicReasonerConfig model to extract all fields
    and map them from v1 format to v0 format automatically.

    IMPORTANT: In v0 config, "engine" is actually the reasoner instance config.
               In v1 config, this was renamed to "reasoner".

    Mappings:
    - Base reasoner fields → v0 engine config:
      - reasoners.logic.name → engine
      - reasoners.logic.size → engine_size
      - reasoners.logic.query_timeout_mins → query_timeout_mins

    - Rule execution fields → v0 reasoner.rule config:
      - reasoners.logic.use_lqp → reasoner.rule.use_lqp
      - reasoners.logic.incremental_maintenance → reasoner.rule.incremental_maintenance
      - etc.

    Returns:
        Dict mapping v1 reasoner paths to v0 config paths

    Example:
        >>> mappings = _get_reasoner_field_mappings()
        >>> mappings["reasoners.logic.size"]
        'engine_size'
        >>> mappings["reasoners.logic.use_lqp"]
        'reasoner.rule.use_lqp'
    """
    mappings = {}

    for field_name, field_info in LogicReasonerConfig.model_fields.items():
        # Base ReasonerConfig fields map to old "engine" config
        # (Because "engine" in old config is actually the reasoner instance)
        if field_name == "name":
            mappings["reasoners.logic.name"] = "engine"
        elif field_name == "size":
            mappings["reasoners.logic.size"] = "engine_size"
        elif field_name == "query_timeout_mins":
            mappings["reasoners.logic.query_timeout_mins"] = "query_timeout_mins"
        # Handle nested lqp config
        elif field_name == "lqp":
            lqp_type = field_info.annotation

            if hasattr(lqp_type, "model_fields"):
                for lqp_field in lqp_type.model_fields.keys():  # type: ignore[attr-defined]
                    # Map reasoners.logic.lqp.{field} → reasoner.rule.lqp.{field}
                    new_path = f"reasoners.logic.lqp.{lqp_field}"
                    old_path = f"reasoner.rule.lqp.{lqp_field}"
                    mappings[new_path] = old_path
        else:
            # Map reasoners.logic.{field} → reasoner.rule.{field}
            # (use_lqp, incremental_maintenance, emit_constraints)
            new_path = f"reasoners.logic.{field_name}"
            old_path = f"reasoner.rule.{field_name}"
            mappings[new_path] = old_path

    return mappings


def _reverse_field_mappings() -> Dict[str, str]:
    """
    Reverse FIELD_MAPPINGS to create v1_path → v0_path mapping.

    FIELD_MAPPINGS: {"v0_key": (section, field)} → {"section.field": "v0_key"}
    Section=None means top-level field.
    """
    reverse = {}
    for v0_key, (section, v1_field) in FIELD_MAPPINGS.items():
        # Build v1 path: section.field or just field if section is None
        if section is None:
            v1_path = v1_field
        else:
            v1_path = f"{section}.{v1_field}"
        reverse[v1_path] = v0_key
    return reverse


def _get_nested_value(obj: Any, path: str) -> Any:
    """
    Navigate nested attributes using dot notation.

    Returns None if path doesn't exist or any intermediate value is None.
    Example: _get_nested_value(config, "reasoners.logic.size") → "HIGHMEM_X64_S"
    """
    parts = path.split(".")
    current = obj

    for part in parts:
        if current is None:
            return None
        try:
            current = getattr(current, part)
        except AttributeError:
            return None

    return current


def _unwrap_secret(value: Any) -> Any:
    """Unwrap Pydantic SecretStr if needed, otherwise return as-is."""
    return value.get_secret_value() if hasattr(value, "get_secret_value") else value


def _extract_connection_fields(connection: ConnectionConfig) -> Dict[str, Any]:
    """
    Flatten connection config to v0 format top-level fields.

    Maps authenticators: username_password → snowflake, jwt/snowflake_jwt → snowflake_jwt, etc.
    Unwraps SecretStr for passwords/tokens. Filters out None values.
    """
    result = {}

    # Map connection type to platform
    conn_type = connection.type

    if conn_type == "snowflake":
        result["platform"] = "snowflake"

        # Add Snowflake-specific fields
        result["account"] = getattr(connection, "account", None)
        result["user"] = getattr(connection, "user", None)
        result["warehouse"] = getattr(connection, "warehouse", None)
        result["role"] = getattr(connection, "role", None)
        result["database"] = getattr(connection, "database", None)
        result["schema"] = getattr(connection, "schema_", None)
        result["rai_app_name"] = getattr(connection, "rai_app_name", None)

        # Map authenticator to old format (2 values differ)
        auth = getattr(connection, "authenticator", "username_password")
        auth_mapping = {
            "username_password": "snowflake",
            "jwt": "snowflake_jwt",
            "snowflake_jwt": "snowflake_jwt",
        }
        result["authenticator"] = auth_mapping.get(auth, auth)

        # Add auth-specific fields based on authenticator type
        if auth in ("username_password", "username_password_mfa"):
            password = getattr(connection, "password", None)
            if password is not None:
                result["password"] = _unwrap_secret(password)
        elif auth in ("jwt", "snowflake_jwt"):
            private_key_path = getattr(connection, "private_key_path", None)
            if private_key_path is not None:
                result["private_key_file"] = private_key_path

            passphrase = getattr(connection, "private_key_passphrase", None)
            if passphrase is not None:
                result["private_key_file_pwd"] = _unwrap_secret(passphrase)
        elif auth == "oauth":
            token = getattr(connection, "token", None)
            if token is not None:
                result["token"] = _unwrap_secret(token)
        elif auth == "programmatic_access_token":
            # v0 SnowflakePATHandler requires token_file_path and reads the file itself
            token_file_path = getattr(connection, "token_file_path", None)
            if token_file_path is not None:
                result["token_file_path"] = token_file_path

    elif conn_type == "duckdb":
        result["platform"] = "duckdb"
        result["path"] = getattr(connection, "path", ":memory:")

    elif conn_type == "local":
        result["platform"] = "local"
        result["host"] = getattr(connection, "host", "localhost")
        result["port"] = getattr(connection, "port", 8010)

    return {k: v for k, v in result.items() if v is not None}


def _apply_v0_defaults(result: Dict[str, Any], platform: str) -> None:
    """
    Apply v0 config defaults for missing fields (modifies result in-place).

    Adds engine_size default based on account type (internal vs non-internal).
    Adds authenticator default ("snowflake") for Snowflake platform.
    Adds legacy jobs defaults expected by v0 executors.
    """
    # Import here to avoid circular dependency. The legacy v0 package is not always
    # available in newer development environments, so keep a small local fallback.
    try:
        legacy_config = importlib.import_module("v0.relationalai.clients.config")
        legacy_default_engine_size = cast(Any, getattr(legacy_config, "default_engine_size"))
    except ModuleNotFoundError:
        legacy_default_engine_size = None

    # Apply engine_size default if missing
    if "engine_size" not in result:
        # Check if internal account (starts with "NDSOEBE")
        account = result.get("account", "")
        is_internal = isinstance(account, str) and account.upper().startswith("NDSOEBE")
        # Cast platform to expected literal type for old config
        platform_literal = cast(Literal['snowflake', 'azure'], platform)
        if legacy_default_engine_size is None:
            result["engine_size"] = "S" if is_internal else "HIGHMEM_X64_S"
        else:
            result["engine_size"] = str(legacy_default_engine_size(platform_literal, internal=is_internal))

    # Apply authenticator default if missing
    if "authenticator" not in result and platform == "snowflake":
        result["authenticator"] = "snowflake"

    # Preserve the legacy v0 jobs defaults even when the v1 config did not
    # explicitly set the jobs section. Some v0 consumers expect these keys to
    # always exist in the translated config.
    result.setdefault("print_txn_progress", True)
    result.setdefault("print_txn_progress_internal", False)
    result.setdefault("enable_guard_rails", False)


def _nest_sections(flat_dict: Dict[str, Any], sections_to_nest: list[str]) -> Dict[str, Any]:
    """
    Convert flat dict keys to nested structure for specific sections.

    Example:
        Input:  {"dry_run": False, "use_lqp": False}
        Sections: ["compiler", "reasoner"]
        Output: {"compiler": {"dry_run": False}, "reasoner": {"rule": {"use_lqp": False}}}

    Args:
        flat_dict: Flat dictionary with top-level keys
        sections_to_nest: List of section prefixes to nest (e.g., ["compiler", "reasoner"])

    Returns:
        Dictionary with specified sections nested
    """
    result = {}

    for key, value in flat_dict.items():
        nested = False

        # Check if this key belongs to any section that should be nested
        for section in sections_to_nest:
            # Check FIELD_MAPPINGS to see if this key maps to this section
            if key in FIELD_MAPPINGS:
                v1_section, v1_field = FIELD_MAPPINGS[key]
                if v1_section == section:
                    # Create nested structure
                    if section not in result:
                        result[section] = {}
                    result[section][v1_field] = value
                    nested = True
                    break

            # Check reasoner field mappings
            reasoner_mappings = _get_reasoner_field_mappings()
            for v1_path, v0_path in reasoner_mappings.items():
                if v0_path == key and v0_path.startswith("reasoner."):
                    # Create nested reasoner structure
                    parts = v0_path.split(".")
                    current = result
                    for part in parts[:-1]:
                        if part not in current:
                            current[part] = {}
                        current = current[part]
                    current[parts[-1]] = value
                    nested = True
                    break

            if nested:
                break

        # If not nested, keep at top-level
        if not nested:
            result[key] = value

    return result


def translate_v1_to_v0(config: Config) -> Dict[str, Any]:
    """
    Translate Config v1 to Config v0 dict format.

    This is a TEMPORARY compatibility layer for v0 executors.
    DELETE when all executors are migrated to v1 config.

    The translation process:
    1. Extract and flatten default connection fields
    2. Reverse FIELD_MAPPINGS to get v1→v0 path mapping
    3. Walk mappings and extract values from v1 config (includes top-level fields)
    4. Nest specific sections (compiler, reasoner) for v0 config.get() compatibility
    5. Apply v0 config defaults for missing fields

    Args:
        config: V1 config (Config instance) to translate

    Returns:
        Dict compatible with v0.relationalai.clients.config.Config
        Can be passed to V0Config(dict) to create v0 config instance

    Raises:
        Exception: If config has no connections (validation should prevent this)

    Example:
        >>> from relationalai.config import create_config
        >>> v1_config = create_config()
        >>> v0_dict = translate_v1_to_v0(v1_config)
    """
    result = {}

    # 1. Get default connection and flatten
    try:
        default_conn = config.get_default_connection()
        conn_fields = _extract_connection_fields(default_conn)
        result.update(conn_fields)
    except Exception:
        # If no connection, we'll just skip connection fields
        # This shouldn't happen with valid config, but be defensive
        pass

    # 2. Reverse field mappings
    reverse_mappings = _reverse_field_mappings()

    # 3. Walk mappings and extract values
    # NOTE: We skip reasoners.logic.* fields here because they are handled
    # by _get_reasoner_field_mappings() which dynamically extracts ALL reasoner
    # fields (including ones not in FIELD_MAPPINGS like use_lqp, lqp.*, etc.)
    for v1_path, v0_key in reverse_mappings.items():
        # Skip reasoners.logic fields - they're handled by reasoner mappings
        if v1_path.startswith("reasoners.logic."):
            continue
        value = _get_nested_value(config, v1_path)
        if value is not None:
            result[v0_key] = value

    # 3b. Handle reasoner fields (dynamically extracted from model)
    reasoner_mappings = _get_reasoner_field_mappings()
    for v1_path, v0_key in reasoner_mappings.items():
        value = _get_nested_value(config, v1_path)
        if value is not None:
            result[v0_key] = value

    # 4. Nest specific sections for v0 config.get() compatibility
    # Add more sections here as needed when v0 code uses config.get("section.field")
    result = _nest_sections(result, ["compiler", "reasoner"])

    # 5. Apply v0 defaults for missing fields
    platform = result.get("platform", "snowflake")
    _apply_v0_defaults(result, platform)

    # 6. Validate direct_access auth compatibility — emits v1 warning and
    #    sets use_direct_access=False so v0 never fires its own warning.
    validate_direct_access_auth(config, result)

    return result
