import pandas as pd
import streamlit as st
from pathlib import Path
from sklearn.ensemble import RandomForestClassifier, IsolationForest
from sklearn.model_selection import train_test_split
from sklearn.metrics import confusion_matrix, classification_report, precision_score, recall_score, f1_score
import plotly.express as px

st.set_page_config(page_title='DDoS Traffic Pattern Explorer', page_icon="🛡️", layout="wide")
st.title("🛡️ DDoS Traffic Pattern Explorer")
st.info('Synthetic high-volume traffic patterns are not evidence of an actual DDoS incident.')
st.caption("For offline education using synthetic data or separately authorized, sanitized feature CSVs. Not for security decisions.")
base=Path(__file__).parent
uploaded=st.file_uploader("Optional: upload sanitized feature CSV with same columns",type=["csv"])
try:
    data=pd.read_csv(uploaded if uploaded is not None else base/'sample_data.csv')
except Exception as exc:
    st.error(f"Could not read CSV: {exc}")
    st.stop()
features=['requests_per_second', 'distinct_sources', 'avg_packet_bytes', 'syn_ratio', 'error_rate', 'duration_s', 'bytes_received']
missing=[x for x in features if x not in data.columns]
if missing:
    st.error(f"Missing columns: {missing}")
    st.stop()
X=data[features].apply(pd.to_numeric,errors='coerce').replace([float('inf'),float('-inf')],float('nan'))
if X.isna().any().any():
    st.error("Features must be numeric, finite, and non-empty. Clean the CSV before continuing.")
    st.stop()
if len(X)<40:
    st.error("At least 40 rows are required for this educational exercise.")
    st.stop()
if 'ddos'=='anomaly':
    model=IsolationForest(n_estimators=120,contamination=0.12,random_state=42)
    predicted=(model.fit_predict(X)==-1).astype(int)
    scores=-model.decision_function(X)
    st.metric("Unusual flows (illustrative)",int(predicted.sum()))
    st.caption("Anomaly scores are not calibrated attack probabilities. The model fits on the uploaded dataset.")
    data['anomaly_score']=scores
    data['flagged']=predicted
    if 'suspicious' in data.columns:
        y=pd.to_numeric(data['suspicious'],errors='coerce')
        if y.notna().all() and set(y.unique()).issubset({0,1}):
            st.write("Comparison against supplied labels (not independent validation):")
            st.write(classification_report(y,predicted,zero_division=0,output_dict=True))
else:
    if 'suspicious' not in data.columns:
        st.error("For supervised classification, include a 'suspicious' column with 0/1 labels.")
        st.stop()
    y=pd.to_numeric(data['suspicious'],errors='coerce')
    if y.isna().any() or set(y.unique())!={0,1} or y.value_counts().min()<3:
        st.error("The target must contain both 0 and 1, with at least three rows of each class.")
        st.stop()
    X_train,X_test,y_train,y_test=train_test_split(X,y,test_size=.25,random_state=42,stratify=y)
    model=RandomForestClassifier(n_estimators=120,max_depth=7,class_weight='balanced',random_state=42)
    model.fit(X_train,y_train)
    predicted=model.predict(X_test)
    prob=model.predict_proba(X_test)[:,1]
    a,b,c=st.columns(3)
    a.metric("Holdout precision",f"{precision_score(y_test,predicted,zero_division=0):.2f}")
    b.metric("Holdout recall",f"{recall_score(y_test,predicted,zero_division=0):.2f}")
    c.metric("Holdout F1",f"{f1_score(y_test,predicted,zero_division=0):.2f}")
    st.caption("Random stratified holdout is illustrative. Real network telemetry needs time/site-separated testing and leakage checks.")
    cm=confusion_matrix(y_test,predicted,labels=[0,1])
    st.plotly_chart(px.imshow(cm,text_auto=True,labels=dict(x='Predicted',y='Actual',color='Count'),x=['Normal','Flagged'],y=['Normal','Flagged'],title='Held-out confusion matrix'),use_container_width=True)
    importance=pd.DataFrame({'feature':features,'importance':model.feature_importances_}).sort_values('importance')
    st.plotly_chart(px.bar(importance,x='importance',y='feature',orientation='h',title='Model feature importance (not causal explanation)'),use_container_width=True)
    st.subheader("Inspect a held-out record")
    idx=st.selectbox('Sample row',range(len(X_test)),format_func=lambda i:f'Test record {i+1}')
    st.dataframe(X_test.iloc[[idx]],use_container_width=True)
    st.write(f"Flag probability: {prob[idx]:.1%} (not calibrated)")
st.subheader("Explore sample data")
st.dataframe(data.head(30),use_container_width=True)
st.download_button("Download current sample CSV",data.to_csv(index=False).encode('utf-8'),file_name='cybersecurity_features.csv',mime='text/csv')
st.warning("Never upload sensitive plant logs, credentials, IP inventories, executable malware or unapproved operational data.")
