"""Abstract base classes for detectors, models, and other components."""

from abc import ABC, abstractmethod
from typing import Any, Dict, List, Optional

import pandas as pd

from src.core.exceptions import DetectorError, ModelError


class BaseDetector(ABC):
    """Abstract base class for pattern detectors."""

    def __init__(self, config: Optional[Dict[str, Any]] = None):
        """
        Initialize detector.

        Args:
            config: Detector configuration dictionary
        """
        self.config = config or {}
        self.enabled = self.config.get("enabled", True)

    @abstractmethod
    def detect(self, data: pd.DataFrame) -> List[Dict[str, Any]]:
        """
        Detect patterns in OHLCV data.

        Args:
            data: DataFrame with OHLCV data (columns: timestamp, open, high, low, close, volume)

        Returns:
            List of detected patterns, each as a dictionary with pattern metadata

        Raises:
            DetectorError: If detection fails
        """
        pass

    def validate_data(self, data: pd.DataFrame) -> None:
        """
        Validate input data format.

        Args:
            data: DataFrame to validate

        Raises:
            DetectorError: If data is invalid
        """
        required_columns = ["timestamp", "open", "high", "low", "close"]
        missing_columns = [col for col in required_columns if col not in data.columns]
        if missing_columns:
            raise DetectorError(
                f"Missing required columns: {missing_columns}",
                context={"required_columns": required_columns, "data_columns": list(data.columns)},
            )

        if len(data) == 0:
            raise DetectorError("DataFrame is empty")

        # Check for required minimum bars
        min_bars = self.config.get("min_bars", 20)
        if len(data) < min_bars:
            raise DetectorError(
                f"Insufficient data: {len(data)} bars, minimum {min_bars} required",
                context={"data_length": len(data), "min_bars": min_bars},
            )


class BaseModel(ABC):
    """Abstract base class for ML models."""

    def __init__(self, config: Optional[Dict[str, Any]] = None):
        """
        Initialize model.

        Args:
            config: Model configuration dictionary
        """
        self.config = config or {}
        self.model = None
        self.is_trained = False

    @abstractmethod
    def train(self, X: pd.DataFrame, y: pd.Series) -> Dict[str, Any]:
        """
        Train the model.

        Args:
            X: Feature matrix
            y: Target labels

        Returns:
            Dictionary with training metrics

        Raises:
            ModelError: If training fails
        """
        pass

    @abstractmethod
    def predict(self, X: pd.DataFrame) -> Any:
        """
        Make predictions.

        Args:
            X: Feature matrix

        Returns:
            Predictions (class labels or probabilities)

        Raises:
            ModelError: If prediction fails
        """
        pass

    @abstractmethod
    def evaluate(self, X: pd.DataFrame, y: pd.Series) -> Dict[str, float]:
        """
        Evaluate model performance.

        Args:
            X: Feature matrix
            y: True labels

        Returns:
            Dictionary with evaluation metrics

        Raises:
            ModelError: If evaluation fails
        """
        pass

    def save(self, path: str) -> None:
        """
        Save model to disk.

        Args:
            path: Path to save model

        Raises:
            ModelError: If save fails
        """
        if not self.is_trained:
            raise ModelError("Cannot save untrained model")
        # Implementation in subclasses

    def load(self, path: str) -> None:
        """
        Load model from disk.

        Args:
            path: Path to load model from

        Raises:
            ModelError: If load fails
        """
        # Implementation in subclasses


class BaseFeatureEngineering(ABC):
    """Abstract base class for feature engineering."""

    def __init__(self, config: Optional[Dict[str, Any]] = None):
        """
        Initialize feature engineering.

        Args:
            config: Configuration dictionary
        """
        self.config = config or {}

    @abstractmethod
    def extract_features(self, data: pd.DataFrame, pattern: Dict[str, Any]) -> Dict[str, float]:
        """
        Extract features for a pattern.

        Args:
            data: OHLCV data
            pattern: Pattern metadata dictionary

        Returns:
            Dictionary of feature names and values

        Raises:
            DataError: If feature extraction fails
        """
        pass

