# ============================================================
# CLUSTERING SENSOR BERDASARKAN FITUR SPC-ALIGNED RULE 1–8
# Dengan penyimpanan:
# - scaler_spc_cluster.pkl
# - pca_spc_cluster.pkl
# - kmeans_spc_cluster.pkl
# - cluster_centroids.xlsx
# ============================================================

# =========================================
# MOUNT GOOGLE DRIVE
# =========================================
from google.colab import drive
drive.mount('/content/drive')

# =========================================
# IMPORT LIBRARY
# =========================================
import os
import joblib
import pandas as pd
import numpy as np

from sklearn.preprocessing import StandardScaler
from sklearn.cluster import KMeans
from sklearn.metrics import silhouette_score
from sklearn.decomposition import PCA

import matplotlib.pyplot as plt
import seaborn as sns

# =========================================
# PATH CONFIG
# =========================================
BASE_DIR = "/content/drive/MyDrive/Data_percobaan"

input_path = os.path.join(
    BASE_DIR,
    "Karakteristik_Sinyal_SPC_Aligned_FULL_v2.xlsx"
)

output_excel_path = os.path.join(
    BASE_DIR,
    "HASIL_CLUSTERING_SENSOR_ALL_SPC_ALIGNED.xlsx"
)

plot_path = os.path.join(
    BASE_DIR,
    "cluster_visualization_spc_aligned.png"
)

scaler_path = os.path.join(
    BASE_DIR,
    "scaler_spc_cluster.pkl"
)

pca_path = os.path.join(
    BASE_DIR,
    "pca_spc_cluster.pkl"
)

kmeans_path = os.path.join(
    BASE_DIR,
    "kmeans_spc_cluster.pkl"
)

centroid_path = os.path.join(
    BASE_DIR,
    "cluster_centroids.xlsx"
)

# =========================================
# LOAD DATA
# =========================================
df = pd.read_excel(input_path)

# =========================================
# PILIH FITUR SPC-ALIGNED
# =========================================
features = [

    # =========================
    # SPC-1
    # =========================
    "STD",
    "Kurtosis",
    "Skewness",
    "Max_Z_Score",
    "Max_Deviation_from_CL",

    # =========================
    # SPC-2
    # =========================
    "Autocorr_Lag1",
    "Max_Run_Above_CL",
    "Max_Run_Below_CL",
    "Max_Run_Length_Total",
    "Run_Length_Mean",
    "Run_Length_STD",
    "Run_Length_Max",
    "Run_Count",

    # =========================
    # SPC-3
    # =========================
    "Trend_Slope",
    "Mann_Kendall_Tau",
    "Mann_Kendall_pvalue",
    "Longest_Monotonic_Sequence",

    # =========================
    # SPC-4
    # =========================
    "Zero_Crossing_Rate_CL",
    "Spectral_Entropy",
    "Alternation_Count",
    "Sign_Change_Frequency",

    # =========================
    # SPC-5
    # =========================
    "Rolling_STD_max",
    "Proportion_Above_2Sigma",
    "High_Deviation_Cluster_Rule5",

    # =========================
    # SPC-6
    # =========================
    "Rolling_STD_mean",
    "Rolling_Mean_Deviation_mean",
    "Proportion_Above_1Sigma",
    "Consecutive_Above_1Sigma",
    "Deviation_Cluster_Rule6",

    # =========================
    # SPC-7
    # =========================
    "PE",
    "SE",
    "Proportion_Within_1Sigma",
    "Low_Variance_Duration_Within_1Sigma",

    # =========================
    # SPC-8
    # =========================
    "Spread_Index",
]

# =========================================
# VALIDASI FITUR
# =========================================
missing_features = [
    f for f in features if f not in df.columns
]

if len(missing_features) > 0:
    raise ValueError(
        f"Fitur berikut tidak ditemukan:\n{missing_features}"
    )

X = df[features].copy()

# =========================================
# HANDLE NaN / INF
# =========================================
X = X.replace([np.inf, -np.inf], np.nan)
X = X.fillna(X.mean())

# =========================================
# NORMALISASI
# =========================================
scaler = StandardScaler()

X_scaled = scaler.fit_transform(X)

# =========================================
# SAVE SCALER
# =========================================
joblib.dump(scaler, scaler_path)

# =========================================
# EVALUASI JUMLAH CLUSTER
# =========================================
cluster_eval = []

for k in range(2, 7):

    kmeans_temp = KMeans(
        n_clusters=k,
        random_state=42,
        n_init=50
    )

    labels_temp = kmeans_temp.fit_predict(X_scaled)

    score = silhouette_score(
        X_scaled,
        labels_temp
    )

    cluster_eval.append({
        "n_clusters": k,
        "silhouette_score": score
    })

cluster_eval_df = pd.DataFrame(cluster_eval)

# =========================================
# PILIH K TERBAIK
# =========================================
k_best = int(
    cluster_eval_df.loc[
        cluster_eval_df["silhouette_score"].idxmax(),
        "n_clusters"
    ]
)

print("\nBest K:", k_best)
display(cluster_eval_df)

# =========================================
# FINAL KMEANS
# =========================================
kmeans = KMeans(
    n_clusters=k_best,
    random_state=42,
    n_init=50
)

df["Cluster"] = kmeans.fit_predict(X_scaled)

# =========================================
# SAVE KMEANS
# =========================================
joblib.dump(kmeans, kmeans_path)

# =========================================
# LABEL MANUAL LAMA
# =========================================
manual_group = {
    "Sensor 1": "Medium",
    "Sensor 2": "Medium",
    "Sensor 3": "Medium",
    "Sensor 4": "Noisy",
    "Sensor 5": "Noisy",
    "Sensor 6": "Noisy",
    "Sensor 7": "Noisy",
    "Sensor 8": "Structured",
    "Sensor 9": "Spike",
    "Sensor 10": "Structured",
    "Sensor 11": "Structured"
}

df["Manual_Group_Old"] = df["Sensor"].map(
    manual_group
)

# =========================================
# PCA
# =========================================
pca = PCA(n_components=2)

X_pca = pca.fit_transform(X_scaled)

# =========================================
# SAVE PCA
# =========================================
joblib.dump(pca, pca_path)

df["PCA1"] = X_pca[:, 0]
df["PCA2"] = X_pca[:, 1]

explained_variance = pca.explained_variance_ratio_

# =========================================
# CLUSTER SUMMARY
# =========================================
cluster_summary = (
    df.groupby("Cluster")[features]
    .mean()
    .reset_index()
)

cluster_sensor_list = (
    df.groupby("Cluster")["Sensor"]
    .apply(lambda x: ", ".join(x))
    .reset_index()
    .rename(columns={
        "Sensor": "Sensors_in_Cluster"
    })
)

# =========================================
# CENTROID - SCALED SPACE
# =========================================
centroids_scaled = pd.DataFrame(
    kmeans.cluster_centers_,
    columns=features
)

centroids_scaled.insert(
    0,
    "Cluster",
    range(k_best)
)

# =========================================
# CENTROID - ORIGINAL SCALE
# =========================================
centroids_original_values = scaler.inverse_transform(
    kmeans.cluster_centers_
)

centroids_original = pd.DataFrame(
    centroids_original_values,
    columns=features
)

centroids_original.insert(
    0,
    "Cluster",
    range(k_best)
)

# =========================================
# CENTROID - PCA SPACE
# =========================================
centroids_pca_values = pca.transform(
    kmeans.cluster_centers_
)

centroids_pca = pd.DataFrame(
    centroids_pca_values,
    columns=["Centroid_PCA1", "Centroid_PCA2"]
)

centroids_pca.insert(
    0,
    "Cluster",
    range(k_best)
)

# =========================================
# GABUNG INFO CENTROID
# =========================================
centroid_info = centroids_original.merge(
    centroids_pca,
    on="Cluster",
    how="left"
)

centroid_info = centroid_info.merge(
    cluster_sensor_list,
    on="Cluster",
    how="left"
)

# =========================================
# SAVE CENTROID EXCEL
# =========================================
with pd.ExcelWriter(
    centroid_path,
    engine="openpyxl"
) as writer:

    centroids_original.to_excel(
        writer,
        sheet_name="Centroids_Original_Scale",
        index=False
    )

    centroids_scaled.to_excel(
        writer,
        sheet_name="Centroids_Scaled",
        index=False
    )

    centroids_pca.to_excel(
        writer,
        sheet_name="Centroids_PCA",
        index=False
    )

    centroid_info.to_excel(
        writer,
        sheet_name="Centroid_Info",
        index=False
    )

# =========================================
# VISUALISASI PCA
# =========================================
plt.figure(figsize=(10, 7))

sns.scatterplot(
    data=df,
    x="PCA1",
    y="PCA2",
    hue="Cluster",
    style="Manual_Group_Old",
    s=140
)

# Label sensor
for i in range(len(df)):

    plt.text(
        df["PCA1"].iloc[i] + 0.05,
        df["PCA2"].iloc[i] + 0.05,
        df["Sensor"].iloc[i],
        fontsize=9
    )

# Plot centroid
plt.scatter(
    centroids_pca["Centroid_PCA1"],
    centroids_pca["Centroid_PCA2"],
    marker="X",
    s=450,
    linewidths=2,
    label="Cluster Centroid"
)

# Label centroid
for i in range(len(centroids_pca)):

    plt.text(
        centroids_pca["Centroid_PCA1"].iloc[i] + 0.15,
        centroids_pca["Centroid_PCA2"].iloc[i] + 0.15,
        f"Centroid C{centroids_pca['Cluster'].iloc[i]}",
        fontsize=10,
        fontweight="bold"
    )

plt.xlabel(
    f"PCA1 ({explained_variance[0]*100:.2f}%)"
)

plt.ylabel(
    f"PCA2 ({explained_variance[1]*100:.2f}%)"
)

plt.title(
    "SPC-Aligned Feature Clustering of Sensor Signals"
)

plt.grid(alpha=0.3)

plt.tight_layout()

plt.savefig(
    plot_path,
    dpi=300
)

plt.close()

# =========================================
# SAVE HASIL KE EXCEL
# =========================================
with pd.ExcelWriter(
    output_excel_path,
    engine="openpyxl"
) as writer:

    cluster_eval_df.to_excel(
        writer,
        sheet_name="Silhouette_Score",
        index=False
    )

    df.to_excel(
        writer,
        sheet_name="Cluster_Result",
        index=False
    )

    cluster_sensor_list.to_excel(
        writer,
        sheet_name="Cluster_Sensor_List",
        index=False
    )

    cluster_summary.to_excel(
        writer,
        sheet_name="Cluster_Summary",
        index=False
    )

    centroids_original.to_excel(
        writer,
        sheet_name="Centroids_Original_Scale",
        index=False
    )

    centroids_scaled.to_excel(
        writer,
        sheet_name="Centroids_Scaled",
        index=False
    )

    centroids_pca.to_excel(
        writer,
        sheet_name="Centroids_PCA",
        index=False
    )

# =========================================
# OUTPUT
# =========================================
print("\n✅ DONE!")

print("\nSaved Files:")
print(output_excel_path)
print(plot_path)
print(scaler_path)
print(pca_path)
print(kmeans_path)
print(centroid_path)

print("\nCluster Result:")
display(
    df[
        [
            "Sensor",
            "Cluster",
            "Manual_Group_Old",
            "PCA1",
            "PCA2"
        ]
    ]
)

print("\nCluster Sensor List:")
display(cluster_sensor_list)

print("\nCentroid Info:")
display(centroid_info)