import awkward as ak
from sklearn.preprocessing import PolynomialFeatures
from sklearn.linear_model import LinearRegression
from sklearn.model_selection import train_test_split
from sklearn.metrics import mean_squared_error
import numpy as np


def fit_linear_regression_model(
    array: ak.Array,
    target_feat: str,
    features: list[str],
    degree: int,
    keep: list[str] = None,
    keep_only_linear_in: str = "",
    remove: list[str] = None,
    include_bias: bool = False,
    fit_intercept: bool = False,
    test_size=0.2,
    random_state=42,
) -> tuple[LinearRegression, list[str]]:
    """Wrapper around sklearn's LinearRegression with PolynomialFeatures.

    Args:
        array (ak.Array): The data.
        target_feat (str): Target feature to be fitted.
        features (list[str]): Features the target depends on.
        degree (int): Highest order of the polynomial.
        keep (list[str], optional): Monomials to keep. Defaults to None.
        keep_only_linear_in (str, optional): Keep only terms that are linear in this feature. Defaults to "".
        remove (list[str], optional): Monomials to remove. Defaults to None.
        include_bias (bool, optional): Inlcude bias term in polynomial. Defaults to False.
        fit_intercept (bool, optional): Fit zeroth order. Defaults to False.
        test_size (float, optional): Fraction of data used for testing. Defaults to 0.2.
        random_state (int, optional): Defaults to 42.

    Raises:
        NotImplementedError: Simultaneous removing and keeping is not implemented.

    Returns:
        tuple[LinearRegression, list[str]]: The linear regression object and the kept features.
    """
    data = np.column_stack([ak.to_numpy(array[feat]) for feat in features])
    target = ak.to_numpy(array[target_feat])
    X_train, X_test, y_train, y_test = train_test_split(
        data,
        target,
        test_size=test_size,
        random_state=random_state,
    )
    poly = PolynomialFeatures(degree=degree, include_bias=include_bias)
    X_train_model = poly.fit_transform(X_train)
    X_test_model = poly.fit_transform(X_test)
    poly_features = poly.get_feature_names_out(input_features=features)
    if not remove:
        if keep:
            remove = [i for i, f in enumerate(poly_features) if f not in keep]
        elif keep_only_linear_in:
            # remove everything that's not linear in variable
            # the corrections should vanish
            remove = [
                i
                for i, f in enumerate(poly_features)
                if (keep_only_linear_in not in f) or (keep_only_linear_in + "^" in f)
            ]
        else:
            remove = []
    elif remove and keep:
        raise NotImplementedError
    X_train_model = np.delete(X_train_model, remove, axis=1)
    X_test_model = np.delete(X_test_model, remove, axis=1)
    poly_features = np.delete(poly_features, remove)

    lin_reg = LinearRegression(fit_intercept=fit_intercept)
    lin_reg.fit(X_train_model, y_train)
    y_pred_test = lin_reg.predict(X_test_model)
    print(f"Parameterisation for {target_feat}:")
    print("intercept=", lin_reg.intercept_)
    print(
        "coef=",
        dict(
            zip(
                poly_features,
                lin_reg.coef_,
            ),
        ),
    )
    print("r2 score=", lin_reg.score(X_test_model, y_test))
    print("RMSE =", mean_squared_error(y_test, y_pred_test, squared=False))
    print()
    return (lin_reg, poly_features)
