"""Customer churn prioritisation — synthetic portfolio case study.

Run from the project root:
    python public/project-assets/code/customer_churn_analysis.py
"""

from pathlib import Path

import pandas as pd
from sklearn.compose import ColumnTransformer
from sklearn.ensemble import RandomForestClassifier
from sklearn.metrics import accuracy_score, classification_report, roc_auc_score
from sklearn.model_selection import train_test_split
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import OneHotEncoder, StandardScaler


SEED = 1210
DATA = Path("public/project-assets/data/customer_churn.csv")
OUT = Path("public/project-assets/data/customer_churn_scored.csv")


def build_pipeline(numeric: list[str], categorical: list[str]) -> Pipeline:
    preprocessing = ColumnTransformer(
        [
            ("numeric", StandardScaler(), numeric),
            ("categorical", OneHotEncoder(handle_unknown="ignore"), categorical),
        ]
    )
    return Pipeline(
        [
            ("preprocessing", preprocessing),
            (
                "classifier",
                RandomForestClassifier(
                    n_estimators=220,
                    min_samples_leaf=5,
                    random_state=SEED,
                ),
            ),
        ]
    )


def main() -> None:
    customers = pd.read_csv(DATA)
    target = "churned"
    id_column = "customer_id"
    categorical = ["region", "contract_type"]
    numeric = [
        "tenure_months",
        "monthly_spend_gbp",
        "support_tickets_90d",
        "usage_score",
        "payment_failures_12m",
    ]

    if customers[id_column].duplicated().any():
        raise ValueError("customer_id must be unique")
    if customers[numeric + categorical + [target]].isna().any().any():
        raise ValueError("model inputs contain missing values")

    X = customers[numeric + categorical]
    y = customers[target]
    X_train, X_test, y_train, y_test = train_test_split(
        X, y, test_size=0.25, stratify=y, random_state=SEED
    )

    model = build_pipeline(numeric, categorical)
    model.fit(X_train, y_train)
    probabilities = model.predict_proba(X_test)[:, 1]
    predictions = (probabilities >= 0.50).astype(int)

    print(f"Accuracy at 0.50 threshold: {accuracy_score(y_test, predictions):.3f}")
    print(f"ROC-AUC:  {roc_auc_score(y_test, probabilities):.3f}")
    print(classification_report(y_test, predictions, digits=3, zero_division=0))
    if predictions.sum() == 0:
        print(
            "Warning: the 0.50 threshold predicts no churners. "
            "Use ranked capacity bands or tune a threshold against intervention cost."
        )

    scored = customers.copy()
    scored["risk_probability"] = model.predict_proba(X)[:, 1]
    scored["risk_band"] = pd.qcut(
        scored["risk_probability"],
        q=[0, 0.50, 0.80, 0.90, 1.0],
        labels=["Monitor", "Medium", "High", "Priority"],
        duplicates="drop",
    )
    scored["recommended_action"] = scored["risk_band"].map(
        {
            "Priority": "Contact within 48 hours",
            "High": "Add to retention campaign",
            "Medium": "Monitor usage and support",
            "Monitor": "No immediate action",
        }
    )
    scored.sort_values("risk_probability", ascending=False).to_csv(OUT, index=False)

    highest_risk_decile = scored.nlargest(max(1, len(scored) // 10), "risk_probability")
    print(
        "Observed churn in highest-risk decile: "
        f"{highest_risk_decile[target].mean():.1%}"
    )
    print(f"Scored retention queue written to {OUT}")


if __name__ == "__main__":
    main()
