"""Multi-strategy stock screener — Streamlit dashboard.

Screens a universe of stocks through four investing philosophies
(Buffett, Burry, Roaring Kitty, Simons) and ranks them. Free data via
yfinance. Educational tool only — not investment advice.

Run:  streamlit run app.py
"""
from __future__ import annotations

import pandas as pd
import streamlit as st

import theme
from screener import data as data_mod
from screener import sentiment
from screener.engine import screen
from screener.strategies import DESCRIPTIONS, STRATEGIES
from screener.universe import UNIVERSES

st.set_page_config(page_title="Multi-Strategy Stock Screener", page_icon="📈", layout="wide")

theme.inject()


@st.cache_data(show_spinner=False, ttl=60 * 60)
def _resolve_universe(name: str) -> list[str]:
    return UNIVERSES[name]()


@st.cache_data(show_spinner=False, ttl=60 * 30)
def _run_screen(tickers: tuple[str, ...]) -> pd.DataFrame:
    return screen(list(tickers))


@st.cache_data(show_spinner=False, ttl=60 * 30)
def _fetch(ticker: str) -> data_mod.StockData:
    """Deep-dive fetch. Cached so reruns don't re-hit Yahoo — which also keeps
    the sentiment payload (and therefore its cache key) stable between reruns."""
    return data_mod.fetch(ticker)


@st.cache_data(show_spinner="Reading the tape…", ttl=60 * 60 * 6)
def _sentiment(ticker: str, payload: str) -> dict:
    return sentiment.analyze(payload)


# ---------------- Sidebar controls ----------------
with st.sidebar:
    st.header("Controls")
    universe_name = st.selectbox("Universe", list(UNIVERSES.keys()))
    tickers = _resolve_universe(universe_name)
    limit = st.slider("Max tickers to scan", 5, min(len(tickers), 505), min(40, len(tickers)),
                      help="Fewer tickers = faster. Yahoo rate-limits large scans.")
    st.divider()
    st.subheader("Strategy weights")
    st.caption("Reweight the composite to match your own conviction.")
    weights = {name: st.slider(name, 0.0, 2.0, 1.0, 0.1) for name in STRATEGIES}
    st.divider()
    custom = st.text_input("Or screen specific tickers (comma-separated)",
                           placeholder="AAPL, GME, BRK-B")
    run = st.button("Run screen", type="primary", use_container_width=True)
    st.divider()
    theme.soundtrack()

with st.expander("How each lens scores a stock"):
    for name, desc in DESCRIPTIONS.items():
        st.markdown(f"**{name}** — {desc}")
    st.info(
        "Scores are 0–100 relative to hand-tuned metric bands, not buy signals. "
        "Missing data is excluded from the average, not scored. Free Yahoo fundamentals are "
        "sometimes stale or absent — always verify before acting."
    )

# ---------------- Run ----------------
if run:
    if custom.strip():
        scan = [t.strip().upper() for t in custom.split(",") if t.strip()]
    else:
        scan = tickers[:limit]

    prog = st.progress(0.0, text=f"Screening {len(scan)} tickers…")

    # Run without the cache when a live progress bar is wanted; cache the final frame.
    df = screen(scan, progress=lambda d, t: prog.progress(d / t, text=f"Screened {d}/{t}"))
    prog.empty()

    if df.empty:
        st.error("No data returned. Yahoo may be rate-limiting — try fewer tickers.")
        st.stop()

    # Apply custom weights to a composite.
    def _weighted(row):
        vals, ws = [], []
        for name, w in weights.items():
            if pd.notna(row.get(name)) and w > 0:
                vals.append(row[name] * w)
                ws.append(w)
        return round(sum(vals) / sum(ws), 1) if ws else None

    df["Weighted"] = df.apply(_weighted, axis=1)
    df = df.sort_values("Weighted", ascending=False, na_position="last").reset_index(drop=True)

    st.session_state["result"] = df

# ---------------- Results ----------------
df = st.session_state.get("result")
if df is not None and not df.empty:
    st.subheader("Results")

    top_cols = st.columns(4)
    for col, name in zip(top_cols, STRATEGIES):
        best = df.loc[df[name].idxmax()] if df[name].notna().any() else None
        if best is not None:
            col.metric(f"Top {name}", f"{best['Ticker']}", f"{best[name]:.0f}/100")

    st.dataframe(
        df,
        use_container_width=True,
        hide_index=True,
        column_config={
            "Buffett": st.column_config.ProgressColumn("Buffett", min_value=0, max_value=100, format="%.0f"),
            "Burry": st.column_config.ProgressColumn("Burry", min_value=0, max_value=100, format="%.0f"),
            "Roaring Kitty": st.column_config.ProgressColumn("Roaring Kitty", min_value=0, max_value=100, format="%.0f"),
            "Simons": st.column_config.ProgressColumn("Simons", min_value=0, max_value=100, format="%.0f"),
            "Weighted": st.column_config.NumberColumn("Weighted ⭐", format="%.1f"),
            "Price": st.column_config.NumberColumn("Price", format="$%.2f"),
        },
    )

    csv = df.to_csv(index=False).encode("utf-8")
    st.download_button("Download CSV", csv, "screen_results.csv", "text/csv")

    # ---- Per-stock deep dive ----
    st.divider()
    st.subheader("Deep dive")
    pick = st.selectbox("Inspect a ticker", df["Ticker"].tolist())
    if pick:
        sd = _fetch(pick)
        # evaluate once — the display loop and the AI payload share these
        results = {name: evaluate(sd) for name, evaluate in STRATEGIES.items()}
        cols = st.columns(len(STRATEGIES))
        for col, (name, res) in zip(cols, results.items()):
            with col:
                st.markdown(f"**{name}**")
                st.metric("Score", f"{res.score:.0f}/100", res.verdict)
                for s in res.signals:
                    val = "—" if s.value is None else f"{s.value:.3g}"
                    pct = "—" if s.score is None else f"{s.score:.0%}"
                    st.caption(f"{s.name}: `{val}`  ·  {pct}")

        # ---- AI sentiment read ----
        st.divider()
        st.subheader("AI sentiment read")
        if not sentiment.available():
            st.caption("Set `ANTHROPIC_API_KEY` (environment variable, or in "
                       "`.streamlit/secrets.toml`) to enable the AI sentiment read.")
        elif st.button(f"Analyse sentiment — {pick}", type="secondary"):
            try:
                news = sentiment.headlines(pick)
                read = _sentiment(pick, sentiment.payload(sd, results, news))
                st.session_state["sentiment"] = (pick, read, len(news))
            except Exception as exc:  # network, auth, bad response — never crash the app
                st.session_state.pop("sentiment", None)
                st.error(f"Sentiment read failed: {exc}")

        cached = st.session_state.get("sentiment")
        if cached and cached[0] == pick:
            _, read, n_news = cached
            st.info("AI-generated from recent public headlines. Educational only — "
                    "**not investment advice.** Verify every claim before acting.")
            head, body = st.columns([1, 4])
            head.metric("Sentiment", f"{read['score']}/100", read["verdict"])
            with body:
                for b in read["bullets"]:
                    st.markdown(f"- {b}")
            st.markdown(f"**Watch next:** {read['suggestion']}")
            st.caption(f"Based on {n_news} Yahoo Finance headline(s) plus the metrics above.")
else:
    st.info("Set your universe and weights in the sidebar, then **Run screen**.")
