Help me adjust my code such that it is reproducible by using a seed. Also try to improve using grid search to make the model improve. 
Code context:
# Install and import required packages
!pip install keras-tuner --upgrade

# Import required packages
import pandas as pd
import numpy as np

# For data preprocessing
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler

# For performance metrics
from sklearn.metrics import (accuracy_score, precision_score, recall_score, 
                             f1_score, roc_auc_score, classification_report, 
                             confusion_matrix)

# For building the neural network
import tensorflow as tf
from tensorflow import keras
from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import (Conv1D, MaxPooling1D, Flatten, Dense, Dropout, 
                                     BatchNormalization)
from tensorflow.keras.optimizers import Adam
from tensorflow.keras.callbacks import EarlyStopping

# For hyperparameter tuning
import keras_tuner as kt
from keras_tuner.tuners import RandomSearch

# Ensure TensorFlow version is compatible with Keras Tuner
print(f'TensorFlow version: {tf.__version__}')
print(f'Keras Tuner version: {kt.__version__}')

# Read data
data = pd.read_csv('dataset_phishing.csv')

# Define features and target variable
features = ['shortest_word_path', 'ratio_intMedia', 'links_in_tags',
            'nb_hyphens', 'page_rank', 'avg_word_path', 
            'ratio_extHyperlinks', 'longest_words_raw', 'google_index',
            'length_hostname', 'longest_word_host', 
            'domain_registration_length', 'nb_www', 'nb_underscore', 
            'nb_dots', 'ratio_extMedia', 'phish_hints', 'domain_in_title', 
            'web_traffic', 'safe_anchor', 'nb_space', 'shortening_service', 
            'ip', 'domain_age', 'nb_qm', 'nb_hyperlinks', 'nb_slash']

X = data[features]
y = data['status']  # 'status' is 'legitimate' or 'phishing'

# Map the 'status' to numerical labels
label_mapping = {'legitimate': 0, 'phishing': 1}
y = y.map(label_mapping)

# Check for any unmapped values or missing values
if y.isnull().any():
    print("Some 'status' values were not mapped properly. Please check the 'status' column for unexpected values.")
    print(y[y.isnull()])
    # Handle unmapped values if necessary
    # For example, you can drop them or assign a default value
    # y = y.fillna(0)

# Handle missing values in features if any
X.fillna(0, inplace=True)

# Split into training and testing sets (80% train, 20% test)
X_train, X_test, y_train, y_test = train_test_split(
    X, y, test_size=0.20, random_state=42, stratify=y)

# Standardize the features
scaler = StandardScaler()

X_train_scaled = scaler.fit_transform(X_train)
X_test_scaled = scaler.transform(X_test)

# Reshape data to be suitable for Conv1D (samples, timesteps, features)
# Since we have numerical features, we treat each feature as a "time step"
X_train_cnn = X_train_scaled.reshape(-1, X_train_scaled.shape[1], 1)
X_test_cnn = X_test_scaled.reshape(-1, X_test_scaled.shape[1], 1)

# Define the model-building function for Keras Tuner
def build_model(hp):
    model = Sequential()
    
    # First Conv1D layer
    model.add(Conv1D(
        filters=hp.Int('filters_1', min_value=16, max_value=128, step=16),
        kernel_size=hp.Int('kernel_size_1', min_value=2, max_value=5, step=1),
        activation='relu',
        input_shape=(X_train_cnn.shape[1], 1),
        padding='same'
    ))
    model.add(BatchNormalization())
    model.add(MaxPooling1D(pool_size=2))
    model.add(Dropout(hp.Float('dropout_rate_1', min_value=0.0, max_value=0.5, step=0.1)))
    
    # Optional second Conv1D layer
    if hp.Boolean('second_conv_layer'):
        model.add(Conv1D(
            filters=hp.Int('filters_2', min_value=16, max_value=128, step=16),
            kernel_size=hp.Int('kernel_size_2', min_value=2, max_value=5, step=1),
            activation='relu',
            padding='same'
        ))
        model.add(BatchNormalization())
        model.add(MaxPooling1D(pool_size=2))
        model.add(Dropout(hp.Float('dropout_rate_2', min_value=0.0, max_value=0.5, step=0.1)))
    
    model.add(Flatten())
    
    # Dense layers
    for i in range(hp.Int('num_dense_layers', 1, 3)):
        model.add(Dense(
            units=hp.Int('units_' + str(i), min_value=32, max_value=256, step=32),
            activation='relu'
        ))
        model.add(BatchNormalization())
        model.add(Dropout(hp.Float('dropout_rate_dense_' + str(i), min_value=0.0, max_value=0.5, step=0.1)))
    
    # Output layer
    model.add(Dense(1, activation='sigmoid'))
    
    # Compile the model
    model.compile(
        optimizer=Adam(
            hp.Float('lr', min_value=1e-4, max_value=1e-2, sampling='LOG', default=1e-3)
        ),
        loss='binary_crossentropy',
        metrics=['accuracy']
    )
    return model

# Initialize the tuner
tuner = RandomSearch(
    build_model,
    objective='val_accuracy',
    max_trials=20,
    executions_per_trial=1,
    directory='tuner_dir',
    project_name='phishing_url_cnn'
)

# Early stopping callback to prevent overfitting
early_stopping = EarlyStopping(monitor='val_loss', patience=5, restore_best_weights=True)

# Run the hyperparameter search
tuner.search(
    x=X_train_cnn,
    y=y_train,
    epochs=50,
    validation_split=0.2,
    callbacks=[early_stopping],
    verbose=1  # Show progress bar
)

# Get the optimal hyperparameters
best_hps = tuner.get_best_hyperparameters(num_trials=1)[0]
print(f"""
The hyperparameter search is complete. 
The optimal number of filters in the first Conv1D layer is {best_hps.get('filters_1')}, 
kernel size is {best_hps.get('kernel_size_1')}, 
and the learning rate is {best_hps.get('lr')}.
""")

# Build the model with the best hyperparameters
best_model = tuner.hypermodel.build(best_hps)

# Train the best model
history = best_model.fit(
    X_train_cnn, y_train,
    epochs=50,
    validation_split=0.2,
    callbacks=[early_stopping],
    verbose=1  # Show progress bar
)

# Evaluate the model on test data
test_loss, test_acc = best_model.evaluate(X_test_cnn, y_test)
print('Test Accuracy: {:.4f}'.format(test_acc))

# Predict probabilities and classes
y_pred_proba = best_model.predict(X_test_cnn).ravel()
y_pred = (y_pred_proba >= 0.5).astype(int)

# Calculate and print metrics
accuracy = accuracy_score(y_test, y_pred)
precision = precision_score(y_test, y_pred)
recall = recall_score(y_test, y_pred)
f1 = f1_score(y_test, y_pred)
roc_auc = roc_auc_score(y_test, y_pred_proba)

print('\nModel Performance Metrics:')
print('--------------------------')
print('Accuracy  : {:.4f}'.format(accuracy))
print('Precision : {:.4f}'.format(precision))
print('Recall    : {:.4f}'.format(recall))
print('F1 Score  : {:.4f}'.format(f1))
print('ROC AUC   : {:.4f}'.format(roc_auc))

print('\nConfusion Matrix:')
print(confusion_matrix(y_test, y_pred))

print('\nClassification Report:')
print(classification_report(y_test, y_pred))