"""
signals.py — C25 Signal Board & Reporting

Usage:
  python3 signals.py                          # full signal board (last 7 days)
  python3 signals.py top [--days 14]          # top companies by mentions
  python3 signals.py company NOVO-B           # detail view for one company
  python3 signals.py summary                  # quick one-liner per company
  python3 signals.py buy                      # only budget/accessible + leveraged ETPs
"""

import sys
import json
import time
import argparse
import sqlite3
from datetime import datetime, timezone
from pathlib import Path
import yfinance as yf
from db import get_conn, DBConn

C25_PATH = Path(__file__).parent / "c25.json"

_c25_raw = json.loads(C25_PATH.read_text())
C25: dict[str, dict] = {k: v for k, v in _c25_raw.items() if not k.startswith("_")}

_analyst_cache: dict[str, dict] = {}


def get_db() -> DBConn:
    """Return a DBConn wrapper. Schema is managed by db.py."""
    return get_conn()


def analyst_rec(ticker: str) -> dict:
    """Hent analytikernes konsensus fra Yahoo Finance (cachet pr. kørsel)."""
    if ticker in _analyst_cache:
        return _analyst_cache[ticker]
    yf_ticker = C25.get(ticker, {}).get("ticker_yahoo", ticker + ".CO")
    try:
        info  = yf.Ticker(yf_ticker).info
        mean  = info.get("recommendationMean")
        count = info.get("numberOfAnalystOpinions", 0)
        if mean is None:
            result = {"label": "Ukendt", "mean": None, "count": 0}
        elif mean <= 1.5: result = {"label": "STÆRK KØB 🟢",  "mean": mean, "count": count}
        elif mean <= 2.5: result = {"label": "KØB 🟢",        "mean": mean, "count": count}
        elif mean <= 3.5: result = {"label": "HOLD 🟡",       "mean": mean, "count": count}
        elif mean <= 4.5: result = {"label": "SÆLG 🔴",       "mean": mean, "count": count}
        else:             result = {"label": "STÆRK SÆLG 🔴", "mean": mean, "count": count}
    except Exception:
        result = {"label": "Ukendt", "mean": None, "count": 0}
    _analyst_cache[ticker] = result
    return result


SENTIMENT_DA  = {"positive": "POSITIV ↑", "negative": "NEGATIV ↓", "neutral": "NEUTRAL →"}
TIER_DA = {
    "budget":       "under 200 kr — kan købes direkte",
    "accessible":   "200-500 kr   — kan købes direkte",
    "expensive":    "500-2000 kr  — overvej ETP",
    "inaccessible": "over 2000 kr — brug ETP",
}


# ---------------------------------------------------------------------------
# Queries
# ---------------------------------------------------------------------------

def company_stats(db: DBConn, days: int = 7) -> list[dict]:
    """Return per-company aggregated signal stats for the last N days."""
    cutoff = int(time.time()) - days * 86400
    rows = db.execute(
        """SELECT
               s.ticker,
               COUNT(*)                                   AS mention_articles,
               SUM(s.mention_count)                       AS total_mentions,
               AVG(CASE WHEN s.sentiment='positive' THEN s.sentiment_score
                        WHEN s.sentiment='negative' THEN -s.sentiment_score
                        ELSE 0 END)                       AS avg_sentiment,
               SUM(CASE WHEN s.sentiment='positive' THEN 1 ELSE 0 END) AS pos_count,
               SUM(CASE WHEN s.sentiment='negative' THEN 1 ELSE 0 END) AS neg_count,
               SUM(CASE WHEN s.sentiment='neutral'  THEN 1 ELSE 0 END) AS neu_count,
               AVG(a.source_count)                        AS avg_sources,
               MAX(COALESCE(s.signal_score, 0))           AS max_signal,
               SUM(COALESCE(s.alert, 0))                  AS alert_count
           FROM article_signals s
           JOIN articles a ON a.slug = s.article_slug
           WHERE s.analyzed_at >= ?
           GROUP BY s.ticker
           ORDER BY max_signal DESC, mention_articles DESC""",
        (cutoff,),
    ).fetchall()
    return [dict(r) for r in rows]


def company_articles(db: DBConn, ticker: str, days: int = 14) -> list[dict]:
    """Return individual articles mentioning a company."""
    cutoff = int(time.time()) - days * 86400
    rows = db.execute(
        """SELECT
               a.title, a.slug, a.start_date, a.source_count,
               s.sentiment, s.sentiment_score, s.mention_count,
               s.signal_score, s.alert, s.claude_reasoning,
               s.momentum_dir, s.momentum_pct_5d, s.coverage_spread
           FROM article_signals s
           JOIN articles a ON a.slug = s.article_slug
           WHERE s.ticker = ? AND s.analyzed_at >= ?
           ORDER BY COALESCE(s.signal_score,0) DESC, a.source_count DESC""",
        (ticker, cutoff),
    ).fetchall()
    return [dict(r) for r in rows]


# ---------------------------------------------------------------------------
# Display helpers
# ---------------------------------------------------------------------------



def print_signal_board(days: int = 7) -> None:
    db   = get_db()
    rows = company_stats(db, days)
    db.close()

    if not rows:
        print(f"Ingen signaler de seneste {days} dage. Kør: make")
        return

    now_str = datetime.now(timezone.utc).strftime("%d %b %Y  %H:%M")
    W = 66
    print(f"\n{'═'*W}")
    print(f"  MONEYMAKER  ·  Hvad sker der med dine C25 aktier?  ·  {now_str}")
    print(f"{'═'*W}")
    print(f"  (baseret på de seneste {days} dage med nyheder)\n")

    for r in rows:
        ticker  = r["ticker"]
        company = C25.get(ticker, {})
        name    = company.get("name", ticker)
        price   = company.get("price_dkk_approx", "?")
        tier    = company.get("tier", "?")
        etps    = company.get("leveraged", [])

        avg_s   = r["avg_sentiment"] or 0
        sent_da = SENTIMENT_DA.get(
            "positive" if avg_s > 0.1 else ("negative" if avg_s < -0.1 else "neutral"),
            "NEUTRAL →"
        )
        n_art   = r["mention_articles"]
        n_src   = int(r["avg_sources"] or 0)
        sig     = r.get("max_signal") or 0
        alerts  = r.get("alert_count") or 0

        # Analytiker konsensus
        rec = analyst_rec(ticker)

        # Er nyheder og analytikere enige?
        news_bull = avg_s > 0.1
        news_bear = avg_s < -0.1
        ana_bull  = rec["mean"] is not None and rec["mean"] <= 2.5
        ana_bear  = rec["mean"] is not None and rec["mean"] >= 3.5
        if (news_bull and ana_bull) or (news_bear and ana_bear):
            agreement = "✅ Nyheder og eksperter er ENIGE"
        elif (news_bull and ana_bear) or (news_bear and ana_bull):
            agreement = "⚠️  Nyheder og eksperter er UENIGE"
        else:
            agreement = "〰️  Intet klart signal endnu"

        # Kildedækning advarsel
        coverage_warn = "  ⚠️  Kun få mediekilder — usikkert" if n_src < 3 else ""

        # Signal i ord
        if sig >= 0.5:   sig_txt = f"🔥 STÆRKT ({sig:.2f})"
        elif sig >= 0.2: sig_txt = f"📊 Moderat ({sig:.2f})"
        elif sig > 0:    sig_txt = f"🔍 Svagt   ({sig:.2f})"
        else:            sig_txt = f"⬜ Ingen    ({sig:.2f})"

        # ETP info
        etp_info = ""
        if etps and len(etps) >= 2:
            long_e  = next((e for e in etps if e["direction"] == "long"),  etps[0])
            short_e = next((e for e in etps if e["direction"] == "short"), etps[1])
            etp_info = f"\n  │  ETP (gearing 3x):  KØB={long_e['ticker']}  SÆLG={short_e['ticker']}  ({long_e['exchange']})"

        alerts_str = f"\n  │  ⚡ {alerts} alert(s) udløst!" if alerts else ""

        print(f"  ┌─ {name}  ({ticker}) {'─'*max(1, W-6-len(name)-len(ticker))}")
        print(f"  │  Pris:              ca. {price} kr  ·  {TIER_DA.get(tier, tier)}")
        print(f"  │  Eksperter ({rec['count']:>2}):    {rec['label']}")
        print(f"  │  Nyheder ({n_art} art):   {sent_da}  ·  gnsn. {n_src} mediekilder{coverage_warn}")
        print(f"  │  Samlet vurdering:  {agreement}")
        print(f"  │  Signalstyrke:      {sig_txt}{etp_info}{alerts_str}")
        print(f"  └{'─'*W}")
        print()

    print(f"  Forklaring:")
    print(f"  Eksperter  = finansanalytikere der følger aktien (Yahoo Finance)")
    print(f"  Nyheder    = hvad medierne skriver, analyseret med AI (FinBERT + Claude)")
    print(f"  Signal     = kombineret score: nyhedssentiment × kildedækning × kursmomentum")
    print(f"  ETP        = børshandlet certifikat — du køber 3x gearing uden at eje aktien")
    print()


def print_company_detail(ticker: str, days: int = 14) -> None:
    ticker = ticker.upper()
    if ticker not in C25:
        print(f"Ukendt ticker: {ticker}. Tilgængelige: {', '.join(sorted(C25.keys()))}")
        return

    db      = get_db()
    company = C25[ticker]
    stats   = next((r for r in company_stats(db, days) if r["ticker"] == ticker), None)
    arts    = company_articles(db, ticker, days)
    db.close()

    rec   = analyst_rec(ticker)
    price = company.get("price_dkk_approx", "?")
    tier  = company.get("tier", "?")
    etps  = company.get("leveraged", [])

    W = 66
    print(f"\n{'═'*W}")
    print(f"  {company['name']}  ({ticker})")
    print(f"  Sektor: {company['sector']}  ·  ca. {price} kr  ·  {TIER_DA.get(tier, tier)}")
    if etps:
        for e in etps:
            label = "KØB (long)" if e["direction"] == "long" else "SÆLG (short)"
            print(f"  ETP {label}: {e['name']} ({e['ticker']}) — {e['exchange']}")
    print(f"{'─'*W}")
    print(f"\n  Hvad siger eksperterne?")
    print(f"    {rec['label']}  ·  {rec['count']} analytikere følger aktien")
    print(f"\n  Hvad siger nyhederne de seneste {days} dage?")

    if not stats:
        print(f"    Ingen nyheder fundet endnu.")
        return

    avg_s   = stats["avg_sentiment"] or 0
    sent_da = SENTIMENT_DA.get(
        "positive" if avg_s > 0.1 else ("negative" if avg_s < -0.1 else "neutral"), ""
    )
    print(f"    {sent_da}  ·  {stats['mention_articles']} artikler")
    print(f"    Positiv: {stats['pos_count']}  ·  Negativ: {stats['neg_count']}  ·  Neutral: {stats['neu_count']}")
    print(f"\n  Enkeltartikler:")
    print(f"  {'─'*W}")

    for a in arts[:15]:
        sent_da2 = SENTIMENT_DA.get(a["sentiment"], "?")
        sig      = a.get("signal_score") or 0
        n_src    = a.get("source_count") or 0
        low_cov  = "  ⚠️ få kilder" if n_src < 3 else ""
        sig_txt  = f"🔥{sig:.2f}" if sig >= 0.5 else (f"📊{sig:.2f}" if sig >= 0.2 else f"⬜{sig:.2f}")
        print(f"\n  {a['title'][:62]}")
        print(f"    Sentiment: {sent_da2:<14}  Signal: {sig_txt}  Kilder: {n_src}{low_cov}")
        reason = a.get("claude_reasoning", "")
        if reason and reason not in ("(no API key)", ""):
            print(f"    Claude: {reason[:80]}")


def print_buy_signals(days: int = 7) -> None:
    """Vis kun aktier med positivt signal — direkte køb og via ETP."""
    db   = get_db()
    rows = company_stats(db, days)
    db.close()

    W = 66
    print(f"\n{'═'*W}")
    print(f"  HVAD KAN DET BETALE SIG?  ·  de seneste {days} dage")
    print(f"{'═'*W}\n")

    direct = [r for r in rows if C25.get(r["ticker"], {}).get("tier") in ("budget", "accessible")]
    via_etp = [r for r in rows if C25.get(r["ticker"], {}).get("leveraged")]

    def fmt(r: dict, note: str = "") -> None:
        ticker = r["ticker"]
        c      = C25.get(ticker, {})
        avg_s  = r["avg_sentiment"] or 0
        sent   = SENTIMENT_DA.get(
            "positive" if avg_s > 0.1 else ("negative" if avg_s < -0.1 else "neutral"), "")
        rec    = analyst_rec(ticker)
        price  = c.get("price_dkk_approx", "?")
        sig    = r.get("max_signal") or 0
        print(f"  {c.get('name',''):<30} {price:>6} kr")
        print(f"    Eksperter: {rec['label']}")
        print(f"    Nyheder:   {sent}  (signal: {sig:.2f})")
        if note:
            print(f"    {note}")
        print()

    if direct:
        print("  Du kan købe disse DIREKTE (overkommelig pris):")
        print(f"  {'─'*W}")
        for r in sorted(direct, key=lambda x: abs(x["avg_sentiment"] or 0), reverse=True):
            fmt(r)

    if via_etp:
        print("  Via WisdomTree ETP (3x gearing — højere risiko/gevinst):")
        print(f"  {'─'*W}")
        for r in sorted(via_etp, key=lambda x: abs(x["avg_sentiment"] or 0), reverse=True):
            ticker = r["ticker"]
            avg_s  = r["avg_sentiment"] or 0
            etps   = C25.get(ticker, {}).get("leveraged", [])
            if etps:
                direction = "long" if avg_s >= 0 else "short"
                etp = next((e for e in etps if e["direction"] == direction), etps[0])
                fmt(r, note=f"ETP: {etp['ticker']} ({direction} 3x) på {etp['exchange']}")
            else:
                fmt(r)

    if not direct and not via_etp:
        print("  Ingen signaler endnu. Kør: make")


# ---------------------------------------------------------------------------
# CLI
# ---------------------------------------------------------------------------

def main() -> None:
    parser = argparse.ArgumentParser(description="C25 signal board")
    sub    = parser.add_subparsers(dest="cmd")

    p_top = sub.add_parser("top", help="Top companies by mention count")
    p_top.add_argument("--days", type=int, default=7)

    p_co = sub.add_parser("company", help="Detail view for one company")
    p_co.add_argument("ticker")
    p_co.add_argument("--days", type=int, default=14)

    sub.add_parser("summary", help="One-liner per company")

    p_buy = sub.add_parser("buy", help="Affordable + leveraged ETP candidates")
    p_buy.add_argument("--days", type=int, default=7)

    p_board = sub.add_parser("board", help="Full signal board")
    p_board.add_argument("--days", type=int, default=7)

    args = parser.parse_args()

    if args.cmd in (None, "board"):
        days = getattr(args, "days", 7)
        print_signal_board(days)
    elif args.cmd == "top":
        print_signal_board(args.days)
    elif args.cmd == "company":
        print_company_detail(args.ticker, getattr(args, "days", 14))
    elif args.cmd == "buy":
        print_buy_signals(args.days)
    elif args.cmd == "summary":
        print_signal_board(7)


if __name__ == "__main__":
    main()
