import numpy as np
import requests
from io import BytesIO

def shuffle_data(X, y):
    
    ################
    #your code here#
    ################

    return X_shuffled, y_shuffled


def split_data(X, y, n_folds, fold):
    
    ################
    #your code here#
    ################

    return X_train, y_train, X_val, y_val


def train_model_and_predict(X_train, y_train, X_val, lamda):
    
    ################
    #your code here#
    ################

    return y_pred

# Load data
response = requests.get("https://users.uop.gr/~nikolentzos/files/cv.npz")
response.raise_for_status()
data = np.load(BytesIO(response.content))

# Training data
X = data["X_train"]
y = data["y_train"]
print('Size of training matrix:', X.shape)
print('Size of training targets:', y.shape)

# Test data
X_test  = data["X_test"]
y_test  = data["y_test"]
print('Size of test matrix:', X_test.shape)
print('Size of test targets:', y_test.shape)

# Values of lamda to try
lamdas = np.linspace(0.00001, 1, 50).tolist()

# Number of folds K
n_folds = 5

X, y = shuffle_data(X, y)
cv_error = []
for lamda in lamdas:
    cv_loss = 0
    for fold in range(n_folds):
        X_train, y_train, X_val, y_val = split_data(X, y, n_folds, fold)
        y_pred = train_model_and_predict(X_train, y_train, X_val, lamda)
        cv_loss += mean_squared_error(y_val, y_pred)
    cv_error.append(cv_loss / n_folds)

best_lamda = lamdas[np.argmin(cv_error)]

################
#your code here#
################