from sklearn.linear_model import Lasso, Ridge
from sklearn.ensemble import RandomForestRegressor, GradientBoostingRegressor
from sklearn.tree import DecisionTreeRegressor
from sklearn.ensemble import StackingRegressor


def lasso():
    model = Lasso()
    params = {
        'alpha': [0.001 * (10 ** i) for i in range(5)],  # Steps by powers of 10
        'max_iter': [1000 + 1000 * i for i in range(10)],  # Steps by 1000
        'tol': [1e-4 / (10 ** i) for i in range(5)],  # Steps by powers of 10
        'warm_start': [True, False],
        'positive': [True, False],
        'selection': ['cyclic', 'random']
    }
    return model, params


def random_forest(random_state=42):
    model = RandomForestRegressor(random_state=random_state)
    params = {
        'n_estimators': [100 + 100 * i for i in range(10)],  # Steps by 100
        'max_depth': [10 + 10 * i for i in range(4)],  # Steps by 10
        'min_samples_split': [2 + i for i in range(3)],  # Steps by 1
        'min_samples_leaf': [1 + i for i in range(4)],  # Steps by 1
        'max_features': ['sqrt', 'log2'],
        'bootstrap': [True, False],
    }
    return model, params


def gradient_boosting(random_state=42):
    model = GradientBoostingRegressor(random_state=random_state)
    params = {
        'n_estimators': [50, 100, 150, 200, 250, 300],  # Reasonable range for most tasks
        'learning_rate': [0.01, 0.05, 0.1, 0.2],  # More conservative learning rates
        'max_depth': [3, 4, 5, 6],  # Keeps complexity manageable
        'min_samples_split': [2, 3, 4],  # Commonly used values
        'min_samples_leaf': [1, 2, 3, 4],  # Commonly used values
        'subsample': [0.8, 0.9, 1.0],  # Reasonable subsample values
        'max_features': ['sqrt', 'log2'],
    }
    return model, params


def decision_tree_regressor():
    model = DecisionTreeRegressor()
    params = {
        "splitter": ["best", "random"],
        "max_depth": [1 + 2 * i for i in range(6)],  # Steps by 2
        "min_samples_split": [2 + i for i in range(3)],  # Steps by 1
        "min_samples_leaf": [1 + i for i in range(4)],  # Steps by 1
        "min_weight_fraction_leaf": [0.0 + 0.1 * i for i in range(3)],  # Steps by 0.1
        "max_features": [None, "sqrt", "log2"],
        "max_leaf_nodes": [10 * i for i in range(1, 6)],  # Steps by 10
    }
    return model, params


def ridge_regressor(random_state=42):
    model = Ridge(random_state=random_state)
    params = {
        'alpha': [0.001 * (10 ** i) for i in range(5)],  # Steps by powers of 10
        'max_iter': [1000 + 1000 * i for i in range(5)],  # Steps by 1000
        'tol': [1e-4 / (10 ** i) for i in range(5)],  # Steps by powers of 10
        'solver': ['auto'],
        'fit_intercept': [True, False],
        'positive': [True, False]
    }
    return model, params


def stacking_lasso(random_state=42):
    base_learners = [
        ('gb', GradientBoostingRegressor(random_state=random_state)),
        ('dt', DecisionTreeRegressor(random_state=random_state)),
    ]

    model = StackingRegressor(
        estimators=base_learners,
        final_estimator=Lasso(random_state=random_state),
        passthrough=True
    )
    params = {
        # Lasso hyperparameters (slightly expanded search space)
        'final_estimator__alpha': [0.001, 0.01, 0.1],  # Expanded to allow some variation
        'final_estimator__max_iter': [3000, 5000],  # Expanded to allow more iterations
        'final_estimator__tol': [1e-4, 1e-5],  # Expanded tolerance
        'final_estimator__warm_start': [False],  # Fixed
        'final_estimator__positive': [True],  # Fixed
        'final_estimator__selection': ['random'],  # Fixed

        # Gradient Boosting hyperparameters (expanded search space)
        'gb__n_estimators': [50, 100, 200],  # Expanded range
        'gb__learning_rate': [0.01, 0.05, 0.1],  # Expanded range
        'gb__max_depth': [3, 5, 7],  # Expanded range
        'gb__min_samples_split': [2, 4],  # Expanded range
        'gb__min_samples_leaf': [1, 2],  # Expanded range
        'gb__subsample': [0.8, 0.9, 1.0],  # Expanded range
        'gb__max_features': ['sqrt', 'log2'],  # Fixed

        # Decision Tree hyperparameters (expanded search space)
        'dt__max_depth': [11, 15, 20],  # Expanded depth range
        'dt__max_features': ['log2'],  # Fixed
        'dt__max_leaf_nodes': [30, 50, 70],  # Expanded range
        'dt__min_samples_leaf': [1, 2, 4],  # Expanded range
        'dt__min_samples_split': [2, 4, 6],  # Expanded range
        'dt__min_weight_fraction_leaf': [0.1, 0.2],  # Reduced range but still variable
        'dt__splitter': ['best', 'random']  # Allowing both splitting strategies
    }
    return model, params