from dataclasses import dataclass
from typing import Optional
import pandas as pd
from .interface_validator import InterfaceValidator
from .common_val_functions import validate_enum_constraint


@dataclass
class TopolinksContext:
    """Context object for topolinks validation handlers"""

    df: pd.DataFrame
    config: dict
    constraint_config: dict


class ISLValidator(InterfaceValidator):

    def __init__(self, isl_schema):
        super().__init__()
        self.isl_schema = isl_schema

    def validate_topolinks(self, df: pd.DataFrame) -> list:
        """Validate inter-switch links by processing constraints and grouping constraints"""
        if df is None or df.empty:
            return []

        errors = []

        validation_rules = self.isl_schema.get("topo_link_validation_rules", {})
        config = validation_rules.get("config", {})

        # Define constraint handlers
        constraint_handlers = {
            "enum_validation": validate_enum_constraint,
            "allowed_pairs": self._validate_topolinks_allowed_pairs,
            "column_mapping": self._validate_topolinks_column_mapping,
        }

        # Process constraints

        constraints = validation_rules.get("constraints", [])
        for constraint in constraints:
            constraint_type = constraint.get("type")

            handler = constraint_handlers.get(constraint_type)
            if handler:
                # Special handling for validate_enum_constraint (external function)
                if constraint_type == "enum_validation":
                    errors.extend(
                        handler(df=df, constraint=constraint, config=config) or []
                    )
                else:
                    # Use context for internal handlers
                    context = TopolinksContext(
                        df=df,
                        config=config,
                        constraint_config=constraint.get("config", {}),
                    )
                    errors.extend(handler(context) or [])

        return errors

    def _validate_topolinks_allowed_pairs(self, context: TopolinksContext) -> list:
        """Validate allowed pairs for topolinks"""
        errors = []
        # Get configuration
        col_mapping = context.config.get("device_port_mapping", {})
        regex = context.config.get(
            "node_type_regex", r"^(leaf|spine|superspine|borderleaf)"
        )
        allowed_pairs_list = context.constraint_config.get("pairs", [])

        dev_a_col = col_mapping.get("device_a_col", "Network Element A")
        dev_b_col = col_mapping.get("device_b_col", "Network Element B")

        # Extract node types
        def node_type(series):
            return series.str.extract(regex, expand=False)

        df_copy = context.df.copy()
        df_copy["A_type"] = node_type(df_copy[dev_a_col])
        df_copy["B_type"] = node_type(df_copy[dev_b_col])

        # Validate allowed pairs
        allowed = set(frozenset(pair) for pair in allowed_pairs_list)
        pairs = pd.Series(map(frozenset, zip(df_copy["A_type"], df_copy["B_type"])))
        row_validity = pairs.isin(allowed) & (df_copy["A_type"] != df_copy["B_type"])
        invalid = df_copy.loc[~row_validity].assign(
            reason=lambda x: (
                (x["A_type"] == x["B_type"]).map(
                    {
                        True: "same-type connection",
                        False: "disallowed type pairing",
                    }
                )
            )
        )

        if not invalid.empty:
            errors.extend(
                (
                    "Row "
                    + invalid.index.astype(str)
                    + ": "
                    + invalid[dev_a_col]
                    + " ("
                    + invalid["A_type"]
                    + ") ↔ "
                    + invalid[dev_b_col]
                    + " ("
                    + invalid["B_type"]
                    + ") — "
                    + invalid["reason"]
                ).tolist()
            )

        return errors

    def _validate_topolinks_column_mapping(self, context: TopolinksContext) -> list:
        """Validate unique device-port combinations for topolinks"""
        errors = []
        col_mapping = context.config.get("device_port_mapping", {})

        dev_a_col = col_mapping.get("device_a_col", "Network Element A")
        dev_b_col = col_mapping.get("device_b_col", "Network Element B")
        port_a_col = col_mapping.get("port_a_col", "Port on Device A")
        port_b_col = col_mapping.get("port_b_col", "Port on Device B")

        # Validate unique device-port combinations for both sides
        dev_a_errors = self.validate_unique_device_port_group(
            df=context.df,
            device_column_name=dev_a_col,
            port_column_name=port_a_col,
            side_label="A",
        )
        dev_b_errors = self.validate_unique_device_port_group(
            df=context.df,
            device_column_name=dev_b_col,
            port_column_name=port_b_col,
            side_label="B",
        )
        errors.extend(dev_a_errors)
        errors.extend(dev_b_errors)

        return errors

    def validate_isl_interfaces(self, df: pd.DataFrame):
        """Validate ISL interface properties by processing constraints and grouping constraints"""
        if df is None or df.empty:
            return []

        errors = []
        validation_rules = self.isl_schema.get("interface_validation_rules", {})
        config = validation_rules.get("config", {})

        # Define constraint handlers
        constraint_handlers = {
            "enum_validation": validate_enum_constraint,
        }

        # Process non-grouping constraints
        constraints = validation_rules.get("constraints", [])
        for constraint in constraints:
            constraint_type = constraint.get("type")
            handler = constraint_handlers.get(constraint_type)
            if handler:
                errors.extend(
                    handler(df=df, constraint=constraint, config=config) or []
                )

        return errors
