from dataclasses import dataclass
import ipaddress
from .common_val_functions import validate_enum_constraint
import pandas as pd


@dataclass
class NodeTypeGroupContext:
    """Context object for node type grouping validation handlers"""

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


class ToponodesValidator:
    def __init__(self, toponodes_schema: dict):
        self.toponodes_schema = toponodes_schema

    def validate_toponodes(self, df: pd.DataFrame):
        """Validate toponodes section by processing constraints"""
        if df is None or df.empty:
            return []

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

        # Define constraint handlers
        constraint_handlers = {
            "enum_validation": validate_enum_constraint,
            "unique_column": self._validate_unique_network_elements,
        }

        # Define grouping constraint handlers
        grouping_handlers = {
            "node_type_grouping": self._validate_node_type_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_unique_network_elements(
        self, df: pd.DataFrame, constraint: dict, config: dict
    ) -> list:
        """Validate that all network elements in the column are unique"""
        errors = []
        constraint_config = constraint.get("config", {})
        column = constraint_config.get("column", "Network Element")

        if column not in df.columns:
            return errors

        # Find duplicates
        duplicates = df[column][df[column].duplicated(keep=False)]

        if not duplicates.empty:
            # Group by duplicate value to show which rows have the same network element
            for ne_value in duplicates.unique():
                dup_rows = df[df[column] == ne_value]["_excel_row"].tolist()
                errors.append(
                    f"Network Element '{ne_value}' appears multiple times in rows {dup_rows}. "
                    f"Each network element must be unique."
                )

        return errors

    def _validate_node_type_grouping(
        self,
        df: pd.DataFrame,
        strategy: dict,
        config: dict,
    ) -> list:
        """Handler for node_type_grouping - validates nodes by type"""
        errors = []
        if df is None or df.empty:
            return errors

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

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

        handlers = {
            "required_for_type": self._handle_node_type_required_for_type,
        }

        for node_type, group in 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
                context = NodeTypeGroupContext(
                    group=group,
                    node_type=node_type,
                    excel_rows=excel_rows,
                    config=constraint.get("config", {}),
                )

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

        return errors

    def _handle_node_type_required_for_type(
        self,
        context: NodeTypeGroupContext,
    ) -> list:
        """Handle required-for-type constraint for node type grouping"""
        errors = []
        required_type = context.config.get("node_type")
        required_cols = context.config.get("required_columns", [])
        allowed_values_map = context.config.get("allowed_values_for_columns", {})

        if context.node_type != required_type:
            return errors

        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"Node type '{context.node_type}' (row {excel_row}) column '{col}' has invalid or missing value '{cell}'; allowed values for {required_type} are {allowed_values_map.get(col)}"
                        )

        return errors
