"""
Validates interface speeds (ISL and Edge) against platform HW type from Toponodes.

Platform-supported speeds:
- D2L: 1G, 10G, 25G
- D3L: 100G, 50G, 40G, 25G, 10G
- Mixed link (D2L–D3L): common speeds only → 10G, 25G
"""

from typing import Dict, List, Optional, Set, Tuple
import pandas as pd

# Platform-wise supported interface speeds (as in user spec)
PLATFORM_SPEEDS = {
    "D2L": ["1G", "10G", "25G"],
    "D3L": ["100G", "50G", "40G", "25G", "10G"],
}

# Common speeds when link is between D2L and D3L
COMMON_SPEEDS_D2L_D3L: Set[str] = set(PLATFORM_SPEEDS["D2L"]) & set(PLATFORM_SPEEDS["D3L"])  # {"10G", "25G"}

HW_TYPE_COL = "HW Type"
NETWORK_ELEMENT_COL = "Network Element"
NETWORK_ELEMENT_A_COL = "Network Element A"
NETWORK_ELEMENT_B_COL = "Network Element B"
ETHERNET_SPEED_COL = "Ethernet Speed"


def _normalize_speed(s: str) -> str:
    """Normalize speed string for comparison (e.g. 10g -> 10G)."""
    if not s or (isinstance(s, float) and pd.isna(s)):
        return ""
    return str(s).strip().upper()


def _hw_type_to_platform(hw_type: str) -> Optional[str]:
    """Map HW Type value to platform D2L or D3L. Returns None if unknown."""
    if not hw_type or (isinstance(hw_type, float) and pd.isna(hw_type)):
        return None
    s = str(hw_type).strip().upper()
    if "D2L" in s:
        return "D2L"
    if "D3L" in s:
        return "D3L"
    return None


def build_node_to_platform(toponodes_df: pd.DataFrame) -> Tuple[Dict[str, str], List[str]]:
    """
    Build mapping from network element name to platform (D2L or D3L) from Toponodes.

    Returns:
        (node_to_platform, errors)
    """
    errors: List[str] = []
    node_to_platform: Dict[str, str] = {}

    if toponodes_df is None or toponodes_df.empty:
        return node_to_platform, errors

    if NETWORK_ELEMENT_COL not in toponodes_df.columns:
        errors.append("Toponodes section missing 'Network Element' column; cannot validate interface speeds by HW type.")
        return node_to_platform, errors

    if HW_TYPE_COL not in toponodes_df.columns:
        errors.append("Toponodes section missing 'HW Type' column; cannot validate interface speeds by HW type.")
        return node_to_platform, errors

    for idx, row in toponodes_df.iterrows():
        node = row.get(NETWORK_ELEMENT_COL)
        if pd.isna(node) or not str(node).strip():
            continue
        node = str(node).strip()
        hw = row.get(HW_TYPE_COL)
        platform = _hw_type_to_platform(hw) if pd.notna(hw) else None
        if platform is None:
            errors.append(
                f"Toponodes row {idx + 2}: '{node}' has missing or unknown HW Type '{hw}'; "
                "expected HW Type containing D2L or D3L (e.g. 7220 IXR-D2L, 7221 IXR-D3L)."
            )
            continue
        node_to_platform[node] = platform

    return node_to_platform, errors


def _allowed_speeds_for_link(platform_a: Optional[str], platform_b: Optional[str]) -> Optional[Set[str]]:
    """Return allowed speeds for a link between two platforms. None if either platform unknown."""
    if not platform_a or not platform_b:
        return None
    if platform_a == platform_b:
        return set(PLATFORM_SPEEDS[platform_a])
    # Mixed D2L–D3L: common speeds only
    return COMMON_SPEEDS_D2L_D3L


def validate_isl_speeds(
    isl_df: pd.DataFrame,
    node_to_platform: Dict[str, str],
) -> List[str]:
    """
    Validate ISL Ethernet Speed against HW type of Device A and Device B.

    - Same platform (D2L–D2L or D3L–D3L): use that platform's speeds.
    - Mixed (D2L–D3L): only 10G, 25G allowed.
    """
    errors: List[str] = []

    if isl_df is None or isl_df.empty:
        return errors

    if ETHERNET_SPEED_COL not in isl_df.columns:
        return errors

    for idx, row in isl_df.iterrows():
        dev_a = row.get(NETWORK_ELEMENT_A_COL)
        dev_b = row.get(NETWORK_ELEMENT_B_COL)
        speed_raw = row.get(ETHERNET_SPEED_COL)
        speed = _normalize_speed(speed_raw)

        if not speed:
            errors.append(
                f"Interfaces (ISL) row {idx + 2}: Ethernet Speed is missing for link "
                f"'{dev_a}' – '{dev_b}'."
            )
            continue

        platform_a = node_to_platform.get(str(dev_a).strip()) if pd.notna(dev_a) else None
        platform_b = node_to_platform.get(str(dev_b).strip()) if pd.notna(dev_b) else None

        if platform_a is None and dev_a is not None and pd.notna(dev_a):
            errors.append(
                f"Interfaces (ISL) row {idx + 2}: Device A '{dev_a}' not found in Toponodes or has no valid HW Type; "
                "cannot validate Ethernet Speed."
            )
            continue
        if platform_b is None and dev_b is not None and pd.notna(dev_b):
            errors.append(
                f"Interfaces (ISL) row {idx + 2}: Device B '{dev_b}' not found in Toponodes or has no valid HW Type; "
                "cannot validate Ethernet Speed."
            )
            continue

        allowed = _allowed_speeds_for_link(platform_a, platform_b)
        if allowed is None:
            continue

        if speed not in allowed:
            allowed_list = sorted(allowed)
            errors.append(
                f"Interfaces (ISL) row {idx + 2}: Ethernet Speed '{speed_raw}' is not supported for link "
                f"'{dev_a}' ({platform_a}) – '{dev_b}' ({platform_b}). "
                f"Allowed speeds for this HW combination: {allowed_list}."
            )

    return errors


def validate_edge_speeds(
    edge_df: pd.DataFrame,
    node_to_platform: Dict[str, str],
) -> List[str]:
    """
    Validate Edge Ethernet Speed against HW type of the device (single endpoint).
    """
    errors: List[str] = []

    if edge_df is None or edge_df.empty:
        return errors

    if ETHERNET_SPEED_COL not in edge_df.columns:
        return errors

    for idx, row in edge_df.iterrows():
        dev = row.get(NETWORK_ELEMENT_COL)
        speed_raw = row.get(ETHERNET_SPEED_COL)
        speed = _normalize_speed(speed_raw)

        if not speed:
            errors.append(
                f"Interfaces (Edge) row {idx + 2}: Ethernet Speed is missing for device '{dev}'."
            )
            continue

        platform = node_to_platform.get(str(dev).strip()) if pd.notna(dev) else None

        if platform is None and dev is not None and pd.notna(dev):
            errors.append(
                f"Interfaces (Edge) row {idx + 2}: Device '{dev}' not found in Toponodes or has no valid HW Type; "
                "cannot validate Ethernet Speed."
            )
            continue

        allowed = set(PLATFORM_SPEEDS[platform]) if platform else None
        if allowed is None:
            continue

        if speed not in allowed:
            allowed_list = sorted(allowed)
            errors.append(
                f"Interfaces (Edge) row {idx + 2}: Ethernet Speed '{speed_raw}' is not supported for device "
                f"'{dev}' (HW type: {platform}). Allowed speeds: {allowed_list}."
            )

    return errors


def validate_interface_speeds_by_hw(
    toponodes_df: pd.DataFrame,
    isl_df: pd.DataFrame,
    edge_df: pd.DataFrame,
) -> List[str]:
    """
    Run full interface speed validation using Toponodes HW type for both ISL and Edge.

    Call this during validate_sections before any resource creation.
    Returns list of error messages (empty if valid).
    """
    errors: List[str] = []

    node_to_platform, build_errors = build_node_to_platform(toponodes_df)
    errors.extend(build_errors)

    # If we could not build node→platform, skip ISL/Edge speed checks to avoid duplicate noise
    if build_errors:
        return errors

    errors.extend(validate_isl_speeds(isl_df, node_to_platform))
    errors.extend(validate_edge_speeds(edge_df, node_to_platform))

    return errors
