"""
Situation-Only JTBD Analysis (Semantic SPS Version)
---------------------------------------------------
Ce face scriptul:

1. Încarcă:
   - fișierul de keywords Google (competitor)
   - fișierul JTBD (cu coloana `situation`)

2. Construiește:
   - SPS (Situation Problem Score) = semantic friction + failure/friction terms
   - CPCS_S (Competitor Problem Coverage Score - Situation only)
   - gap_score = SPS × (1 - CPCS_S)
   - intent (unaware / pain-aware / problem-aware / solution-aware)

3. Salvează:
   - situation_semantic_output.csv (toate JTBD-urile cu scoruri)
   - top_context_triggers_semantic.csv (top 20 situații cu gap cel mai mare)
"""

import pandas as pd
import numpy as np
from io import StringIO
from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.metrics.pairwise import cosine_similarity
import sys


# =========================================================
# 1. Universal Anchors (Domain-Agnostic)
# =========================================================

FRICTION_ANCHORS = [
    "unexpected behavior",
    "unpredictable outcomes",
    "things break for no clear reason",
    "unclear why it is happening",
    "unstable process",
    "too difficult to control",
    "too many moving pieces",
    "requires too much manual effort",
    "declining performance over time",
    "inconsistent results",
]

NEUTRAL_ANCHORS = [
    "stable predictable system",
    "routine workflow",
    "clear steps",
    "no unexpected changes",
    "easy to control process",
]


# =========================================================
# 2. Explicit Lexicons (Complement to Semantic)
# =========================================================

FAILURE_TERMS = {
    "fails", "breaks", "crashes", "timeouts",
    "loops", "inconsistent", "unreliable",
}

FRICTION_TERMS = {
    "hard", "difficult", "slow", "messy",
    "complex", "unmanageable", "confusing",
}

TECH_TERMS = {
    "framework", "platform", "llm", "agent",
    "tool", "workflow", "pipeline", "api", "sdk",
}


# =========================================================
# 3. Data Loaders
# =========================================================

def load_google_keywords(path: str) -> pd.DataFrame:
    """
    Încarcă fișierul exportat din Google Keyword Planner (UTF-16).
    Ne interesează în principal coloana 'Keyword'.
    """
    raw = pd.read_csv(path, encoding="utf-16")
    text = "\n".join(raw.iloc[1:, 0].astype(str))
    df = pd.read_csv(StringIO(text), sep="\t")

    if "Keyword" not in df.columns:
        raise ValueError("Nu am găsit coloana 'Keyword' în fișierul Google Ads.")

    df = df.rename(columns={"Keyword": "keyword"})
    df["keyword"] = df["keyword"].astype(str).str.lower().fillna("")
    return df


def load_jtbd(path: str) -> pd.DataFrame:
    """
    Încarcă fișierul JTBD.
    Se așteaptă o coloană 'situation' (text situațional).
    """
    df = pd.read_csv(path)
    df["jtbd_id"] = df.index
    if "situation" not in df.columns:
        # dacă nu există, creăm una goală ca fallback
        df["situation"] = ""
    return df


# =========================================================
# 4. Situation Text
# =========================================================

def normalize_text(x):
    return x.lower().strip() if isinstance(x, str) else ""


def extract_situation(df: pd.DataFrame) -> pd.DataFrame:
    df["situation_text"] = df["situation"].astype(str).apply(normalize_text)
    return df


# =========================================================
# 5. TF-IDF + Centroids (friction vs neutral)
# =========================================================

def prepare_vectorizer_and_centroids(jtbd_df: pd.DataFrame):
    """
    Construiește un vectorizator TF-IDF pe:
        - toate situation_text
        - FRICTION_ANCHORS
        - NEUTRAL_ANCHORS

    Calculează centroidul semantic al fricțiunii și al normalității.
    """
    corpus = (
        jtbd_df["situation_text"].tolist()
        + FRICTION_ANCHORS
        + NEUTRAL_ANCHORS
    )

    vectorizer = TfidfVectorizer(
        ngram_range=(1, 2),
        min_df=1,
        max_df=0.95,
    )
    X = vectorizer.fit_transform(corpus)

    N = len(jtbd_df)
    F = len(FRICTION_ANCHORS)

    friction_vecs = X[N: N + F]
    neutral_vecs = X[N + F:]

    # IMPORTANT: transformăm în ndarrays 2D compatibile cu cosine_similarity
    friction_centroid = np.asarray(friction_vecs.mean(axis=0)).reshape(1, -1)
    neutral_centroid = np.asarray(neutral_vecs.mean(axis=0)).reshape(1, -1)

    return vectorizer, friction_centroid, neutral_centroid


# =========================================================
# 6. Semantic SPS + Explicit Terms
# =========================================================

def compute_semantic_friction(text: str,
                              vectorizer: TfidfVectorizer,
                              friction_centroid: np.ndarray,
                              neutral_centroid: np.ndarray) -> float:
    """
    semantic_friction = sim(text, friction_centroid) - sim(text, neutral_centroid)
    """
    if not isinstance(text, str) or not text.strip():
        return 0.0

    vec = vectorizer.transform([text])
    friction_sim = cosine_similarity(vec, friction_centroid)[0][0]
    neutral_sim = cosine_similarity(vec, neutral_centroid)[0][0]
    return float(friction_sim - neutral_sim)


def compute_sps(row,
                vectorizer: TfidfVectorizer,
                friction_centroid: np.ndarray,
                neutral_centroid: np.ndarray) -> float:
    """
    SPS final:
        1.5 × semantic_friction
      + 3   × (# failure_terms)
      + 2   × (# friction_terms explicite)
      - 1   × (# tech_terms)
      + 0.03 × lungime_in_cuvinte
    """
    text = row.get("situation_text", "")
    if not isinstance(text, str):
        text = ""

    # componentă semantică (universală)
    semantic = compute_semantic_friction(text, vectorizer, friction_centroid, neutral_centroid)

    # componentă explicită lexicală
    explicit = (
        3 * sum(t in text for t in FAILURE_TERMS) +
        2 * sum(t in text for t in FRICTION_TERMS) -
        1 * sum(t in text for t in TECH_TERMS) +
        0.03 * len(text.split())
    )

    return 1.5 * semantic + explicit


# =========================================================
# 7. Competitor Coverage (Situation Only)
# =========================================================

def compute_cpcs_s(X_sit, X_kw) -> np.ndarray:
    """
    CPCS_S = media similarităților cosine între fiecare situation_text
             și TOATE keyword-urile competitorului.
    """
    if X_kw.shape[0] == 0:
        # fallback: fără keywords -> coverage zero
        return np.zeros(X_sit.shape[0], dtype=float)

    sim = cosine_similarity(X_sit, X_kw)
    return np.array([float(sim[i].mean()) for i in range(sim.shape[0])])


# =========================================================
# 8. Intent Prediction (heuristic)
# =========================================================

def predict_intent(text: str) -> str:
    if not isinstance(text, str):
        text = ""

    tech_hits = sum(t in text for t in TECH_TERMS)
    fail_hits = sum(t in text for t in FAILURE_TERMS)
    frix_hits = sum(t in text for t in FRICTION_TERMS)

    if tech_hits >= 2:
        return "solution-aware"
    if fail_hits >= 1:
        return "problem-aware"
    if frix_hits >= 1:
        return "pain-aware"
    return "unaware"


# =========================================================
# 9. Main Pipeline
# =========================================================

def main():
    KW_PATH = sys.argv[1]
    JTBD_PATH = sys.argv[2]

    print("Loading data...")
    kw_df = load_google_keywords(KW_PATH)
    jtbd_df = load_jtbd(JTBD_PATH)

    print("Extracting situation text...")
    jtbd_df = extract_situation(jtbd_df)

    print("Preparing vectorizer and semantic centroids...")
    vectorizer, friction_centroid, neutral_centroid = prepare_vectorizer_and_centroids(jtbd_df)

    print("Vectorizing situations and keywords...")
    X_sit = vectorizer.transform(jtbd_df["situation_text"].tolist())
    X_kw = vectorizer.transform(kw_df["keyword"].tolist())

    print("Computing SPS (Situation Problem Score)...")
    jtbd_df["SPS"] = jtbd_df.apply(
        lambda row: compute_sps(row, vectorizer, friction_centroid, neutral_centroid),
        axis=1,
    )

    print("Computing CPCS_S (Competitor Problem Coverage - Situation)...")
    jtbd_df["CPCS_S"] = compute_cpcs_s(X_sit, X_kw)

    print("Computing gap_score (SPS × (1 - CPCS_S))...")
    jtbd_df["gap_score"] = jtbd_df["SPS"] * (1.0 - jtbd_df["CPCS_S"])

    print("Predicting intent from situation text...")
    jtbd_df["intent"] = jtbd_df["situation_text"].apply(predict_intent)

    print("Saving full situation semantic output...")
    jtbd_df.to_csv("situation_semantic_output.csv", index=False)

    print("Saving top 20 context triggers by gap_score...")
    top_triggers = jtbd_df.sort_values(by="gap_score", ascending=False).head(20)
    top_triggers.to_csv("top_context_triggers_semantic.csv", index=False)

    print("DONE — semantic SPS pipeline executed.")


if __name__ == "__main__":
    main()
