"""Platform information standardization.

This module provides utilities for extracting and standardizing platform
information from various adapter types (SQL and DataFrame).

Copyright 2026 Joe Harris / BenchBox Project

Licensed under the MIT License. See LICENSE file in the project root for details.
"""

from __future__ import annotations

from dataclasses import dataclass, field
from typing import Any, Protocol, runtime_checkable


@dataclass
class PlatformInfoInput:
    """Normalized platform information.

    This dataclass represents standardized platform metadata regardless
    of whether the adapter is SQL-based or DataFrame-based.
    """

    name: str  # "DuckDB", "DataFusion", "Polars", etc.
    platform_version: str | None = None  # Platform version string (legacy: may be driver or engine)
    client_library_version: str | None = None  # Client library version string
    execution_mode: str = "sql"  # "sql" or "dataframe"
    connection_mode: str | None = None  # "in-memory", "file", "remote", etc.
    family: str | None = None  # "expression" (Polars) or "pandas" (Pandas-like)
    config: dict[str, Any] = field(default_factory=dict)
    # Engine/service version metadata (separate from Python driver/client version).
    # For coupled platforms (DuckDB, DataFusion): same as platform_version.
    # For decoupled platforms (Snowflake, BigQuery, etc.): the remote engine version.
    engine_version: str | None = None
    engine_version_source: str | None = None  # Provenance: how the engine version was obtained


@runtime_checkable
class StandardPlatformInfo(Protocol):
    """Protocol for platform info providers.

    Adapters can implement this protocol to provide standardized platform
    information directly, rather than relying on extraction heuristics.
    """

    def get_standard_platform_info(self) -> PlatformInfoInput:
        """Return standardized platform information."""
        ...


def build_platform_info(
    adapter: Any,
    execution_mode: str = "sql",
) -> PlatformInfoInput:
    """Build standardized platform info from any adapter.

    Handles both SQL adapters and DataFrame adapters uniformly by:
    1. Checking for StandardPlatformInfo protocol implementation
    2. Extracting from legacy get_platform_info() method
    3. Falling back to attribute inspection

    Args:
        adapter: Platform adapter instance (SQL or DataFrame)
        execution_mode: "sql" or "dataframe"

    Returns:
        Standardized PlatformInfoInput instance
    """
    # Try standard interface first
    if isinstance(adapter, StandardPlatformInfo):
        return adapter.get_standard_platform_info()

    # Extract from legacy get_platform_info()
    legacy_info: dict[str, Any] = {}
    if hasattr(adapter, "get_platform_info"):
        try:
            result = adapter.get_platform_info()
            if isinstance(result, dict):
                legacy_info = result
        except Exception:
            pass  # Ignore errors from get_platform_info

    # Build standardized info
    platform_version = _extract_version(legacy_info)
    client_version = _extract_client_version(legacy_info)
    if client_version is None:
        client_version = "unknown"
    if platform_version is None:
        platform_version = "unknown"

    return PlatformInfoInput(
        name=_extract_platform_name(adapter, legacy_info),
        platform_version=platform_version,
        client_library_version=client_version,
        execution_mode=execution_mode,
        connection_mode=_extract_connection_mode(legacy_info),
        family=_extract_family(adapter, legacy_info),
        config=_extract_config(legacy_info),
    )


def _extract_platform_name(adapter: Any, info: dict[str, Any]) -> str:
    """Extract platform name consistently.

    Priority order:
    1. adapter.platform_name attribute
    2. info["platform_name"]
    3. info["platform"]
    4. info["name"]
    5. Class name (cleaned up)
    """
    # Check adapter attribute first
    if hasattr(adapter, "platform_name"):
        name = adapter.platform_name
        if name:
            return str(name)

    # Check info dict
    for key in ("platform_name", "platform", "name"):
        if key in info and info[key]:
            return str(info[key])

    # Fall back to class name
    class_name = type(adapter).__name__
    # Clean up common suffixes
    for suffix in ("Adapter", "Platform", "DataFrame", "DF"):
        if class_name.endswith(suffix):
            class_name = class_name[: -len(suffix)]
    return class_name or "Unknown"


def _extract_version(info: dict[str, Any]) -> str | None:
    """Extract version from various possible locations."""
    for key in (
        "version",
        "platform_version",
        "embedded_library_version",
    ):
        if key in info and info[key]:
            return str(info[key])
    return None


def _extract_client_version(info: dict[str, Any]) -> str | None:
    for key in ("client_library_version", "client_version", "driver_version"):
        if key in info and info[key]:
            return str(info[key])
    return None


def _extract_connection_mode(info: dict[str, Any]) -> str | None:
    """Extract connection mode from platform info."""
    return info.get("connection_mode")


def _extract_family(adapter: Any, info: dict[str, Any]) -> str | None:
    """Extract DataFrame family (expression vs pandas)."""
    # Check adapter attribute
    if hasattr(adapter, "family"):
        return adapter.family

    # Check info dict
    return info.get("family")


def _extract_config(info: dict[str, Any]) -> dict[str, Any]:
    """Extract configuration, flattening nested structures recursively.

    Excludes keys that are already extracted to other fields.
    """
    config: dict[str, Any] = {}

    # Keys to exclude (already extracted or redundant)
    exclude = {
        "platform_version",
        "version",
        "client_library_version",
        "client_version",
        "driver_version",
        "embedded_library_version",
        "name",
        "platform",
        "platform_name",
        "adapter_name",
        "platform_type",
        "connection_mode",
        "family",
    }

    def flatten_config(d: dict[str, Any], target: dict[str, Any]) -> None:
        """Recursively flatten configuration, handling nested 'configuration' keys."""
        for key, value in d.items():
            if key in exclude:
                continue
            if key == "configuration" and isinstance(value, dict):
                # Recursively flatten nested configuration
                flatten_config(value, target)
            else:
                target[key] = value

    flatten_config(info, config)
    return config


def format_platform_display_name(
    platform_name: str,
    execution_mode: str = "sql",
) -> str:
    """Format platform name for display in results."""
    return platform_name


def merge_platform_info(
    base_info: PlatformInfoInput,
    additional_config: dict[str, Any] | None = None,
) -> PlatformInfoInput:
    """Merge additional configuration into platform info.

    Args:
        base_info: Base platform information
        additional_config: Additional configuration to merge

    Returns:
        New PlatformInfoInput with merged configuration
    """
    if not additional_config:
        return base_info

    merged_config = {**base_info.config, **additional_config}

    return PlatformInfoInput(
        name=base_info.name,
        platform_version=base_info.platform_version,
        client_library_version=base_info.client_library_version,
        execution_mode=base_info.execution_mode,
        connection_mode=base_info.connection_mode,
        family=base_info.family,
        config=merged_config,
        engine_version=base_info.engine_version,
        engine_version_source=base_info.engine_version_source,
    )
