from ._errors import MLDashValidationError

VALID_TASK_TYPES = {'classification', 'regression'}
VALID_FEATURE_TYPES = {'numeric', 'categorical', 'text'}
VALID_FRAMEWORKS = {
    'sklearn', 'xgboost', 'lightgbm', 'catboost',
    'tensorflow', 'pytorch', 'statsmodels', 'other',
}


def validate_model(model):
    if not hasattr(model, 'predict'):
        raise MLDashValidationError(
            "Model does not have a predict() method."
        )
    # Check if model appears to be fitted
    fitted_indicators = ('n_features_in_', 'classes_', 'coef_', 'feature_importances_',
                         'tree_', 'estimators_', 'support_vectors_', 'intercept_')
    if not any(hasattr(model, attr) for attr in fitted_indicators):
        # Keras models: check built state and weights
        keras_fitted = (
            (hasattr(model, 'built') and model.built)
            or (hasattr(model, 'weights') and len(getattr(model, 'weights', [])) > 0)
        )
        if not keras_fitted:
            raise MLDashValidationError(
                "Model does not appear to be fitted. Call model.fit() first."
            )


def validate_name(name):
    if not name or not isinstance(name, str) or not name.strip():
        raise MLDashValidationError("name is required.")


def validate_url(url):
    if not isinstance(url, str) or not (url.startswith('http://') or url.startswith('https://')):
        raise MLDashValidationError("url must start with http:// or https://.")


def validate_api_key(api_key):
    if not isinstance(api_key, str) or not api_key.startswith('mldash_'):
        raise MLDashValidationError("api_key must start with 'mldash_'.")


def validate_scaler(scaler):
    if scaler is not None and not hasattr(scaler, 'transform'):
        raise MLDashValidationError("Scaler must have a transform() method.")


def validate_encoder(encoder):
    if encoder is not None and not hasattr(encoder, 'transform'):
        raise MLDashValidationError("Encoder must have a transform() method.")


def validate_label_map(label_map):
    if label_map is not None:
        if not isinstance(label_map, dict):
            raise MLDashValidationError("label_map must be a dict mapping labels.")
        if label_map and not all(isinstance(k, str) for k in label_map.keys()):
            raise MLDashValidationError("label_map must be a dict with string keys.")


def validate_task_type(task_type):
    if task_type is not None and task_type not in VALID_TASK_TYPES:
        raise MLDashValidationError(
            "task_type must be 'classification' or 'regression'."
        )


def validate_framework(framework):
    if framework is not None and framework not in VALID_FRAMEWORKS:
        raise MLDashValidationError(
            f"Unknown framework. Supported: {', '.join(sorted(VALID_FRAMEWORKS))}."
        )


def validate_features(features):
    if features is not None:
        if not isinstance(features, dict):
            raise MLDashValidationError("features must be a dict.")
        for col, ftype in features.items():
            if ftype not in VALID_FEATURE_TYPES:
                raise MLDashValidationError(
                    f"Feature types must be 'numeric', 'categorical', or 'text'. "
                    f"Got '{ftype}' for '{col}'."
                )


def validate_target(target):
    if target is not None:
        if not isinstance(target, dict) or len(target) != 1:
            raise MLDashValidationError("target must have exactly one entry.")


VALID_METRIC_KEYS = {
    'accuracy', 'precision', 'recall', 'f1_score', 'roc_auc',
    'mse', 'rmse', 'mae', 'r2_score',
}


def validate_metrics(metrics):
    if metrics is not None:
        if not isinstance(metrics, dict):
            raise MLDashValidationError("metrics must be a dict.")
        for key, value in metrics.items():
            if key not in VALID_METRIC_KEYS:
                raise MLDashValidationError(
                    f"Unknown metric '{key}'. "
                    f"Supported: {', '.join(sorted(VALID_METRIC_KEYS))}."
                )
            if not isinstance(value, (int, float)):
                raise MLDashValidationError(
                    f"Metric '{key}' must be a number, got {type(value).__name__}."
                )


def validate_all(model, name, scaler=None, encoder=None, label_map=None,
                 task_type=None, framework=None, features=None, target=None,
                 metrics=None):
    """Run all validation checks. Raises MLDashValidationError on first failure."""
    validate_model(model)
    validate_name(name)
    validate_scaler(scaler)
    validate_encoder(encoder)
    validate_label_map(label_map)
    validate_task_type(task_type)
    validate_framework(framework)
    validate_features(features)
    validate_target(target)
    validate_metrics(metrics)
