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 LAGGroupContext:
    """Context object for LAG grouping validation handlers"""

    group: pd.DataFrame
    lag_id: str
    excel_rows: list
    config: dict


class EdgeInterfaceValidator(InterfaceValidator):

    def __init__(self, edge_schema: dict):
        super().__init__()
        self.edge_schema = edge_schema

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

        errors = []

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

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

        # Define grouping constraint handlers
        grouping_handlers = {
            "lag_grouping": self._validate_lag_grouping,
        }

        # 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 []
                )

        # Process grouping constraints
        grouping_constraints = validation_rules.get("grouping_constraints", [])
        for strategy in grouping_constraints:
            grouping_type = strategy.get("type")
            handler = grouping_handlers.get(grouping_type)
            if handler:
                errors.extend(handler(df=df, strategy=strategy, config=config) or [])

        return errors

    def _validate_edge_column_mapping(
        self, df: pd.DataFrame, constraint: dict, config: dict
    ) -> list:
        """Handler for column_mapping constraint in edge interfaces"""
        errors = []
        constraint_config = constraint.get("config", {})
        device_col = constraint_config.get("device_col", "Network Element")
        port_col = constraint_config.get("port_col", "Port on Device")

        errors.extend(
            self.validate_unique_device_port_group(
                df=df, device_column_name=device_col, port_column_name=port_col
            )
            or []
        )
        return errors

    def _validate_lag_grouping(
        self,
        df: pd.DataFrame,
        strategy: dict,
        config: dict,
    ) -> list:
        """Handler for lag_grouping - validates LAG groups by grouping"""
        errors = []
        if df is None or df.empty:
            return errors

        # Extract strategy-specific parameters
        groupby_column = strategy.get("groupby_column")
        lag_filter = strategy.get("lag_filter", {})
        constraints = strategy.get("constraints", [])

        if not groupby_column or groupby_column not in df.columns:
            return errors

        # Filter for LAG rows
        filter_column = lag_filter.get("column", "Type")
        lag_identifier = lag_filter.get("identifier", "lag")

        if filter_column not in df.columns:
            return errors

        lag_df = df[
            df[filter_column]
            .astype(str)
            .apply(lambda x: lag_identifier in str(x).lower())
        ]

        if lag_df.empty:
            return errors

        # Define per-LAG handlers
        handlers = {
            "min_members": self._handle_lag_min_members,
            "required_for_type": self._handle_lag_required_for_type,
            "consistent_type": self._handle_lag_consistent_type,
        }

        # Group by LAG and validate each group
        for lag_id, group in lag_df.groupby(groupby_column):
            excel_rows = group["_excel_row"].tolist()

            for constraint in constraints:
                constraint_type = constraint.get("type")
                handler = handlers.get(constraint_type)
                if not handler:
                    continue

                # Create context object with all fields populated
                context = LAGGroupContext(
                    group=group,
                    lag_id=lag_id,
                    excel_rows=excel_rows,
                    config=constraint.get("config", {}),
                )

                constraint_errors = handler(context)
                errors.extend(constraint_errors or [])

        return errors

    def _handle_lag_min_members(
        self,
        context: LAGGroupContext,
    ) -> list:
        """Handle minimum members constraint for LAG grouping"""
        errors = []
        min_members = context.config.get("min_lag_members", 2)

        if len(context.group) < min_members:
            errors.append(
                f"LAG {context.lag_id} (rows {context.excel_rows}) has only {len(context.group)} port(s); a lag must have at least {min_members} member ports"
            )

        return errors

    def _handle_lag_required_for_type(
        self,
        context: LAGGroupContext,
    ) -> list:
        """Handle required-for-type constraint for LAG grouping"""
        errors = []
        required_cols = context.config.get("required_columns", [])
        allowed_values_map = context.config.get("allowed_values_for_columns", {})

        # No type check needed - we're already in filtered LAG groups
        for col in required_cols:
            if col in allowed_values_map:
                allowed_vals = [v.lower() for v in allowed_values_map.get(col, [])]
                for idx, cell in context.group[col].items():
                    if pd.isna(cell) or str(cell).strip().lower() not in allowed_vals:
                        excel_row = context.group.loc[idx, "_excel_row"]
                        errors.append(
                            f"LAG {context.lag_id} (row {excel_row}) column '{col}' has invalid or missing value '{cell}'; allowed values are {allowed_values_map.get(col)}"
                        )

        return errors

    def _handle_lag_consistent_type(
        self,
        context: LAGGroupContext,
    ) -> list:
        """Handle consistent type constraint for LAG grouping"""
        errors = []
        column = context.config.get("column")

        if not column or column not in context.group.columns:
            return errors

        if context.group[column].nunique() != 1:
            errors.append(
                f"LAG {context.lag_id} (rows {context.excel_rows}) has differing {column}"
            )

        return errors
