"""
portfolio.py — Position tracker + ordre-forslag

Kommandoer:
  python portfolio.py orders          → dagens køb/sælg/hold forslag
  python portfolio.py status          → åbne positioner + stop/take
  python portfolio.py buy TICKER N PRIS  → registrer et køb
  python portfolio.py sell TICKER PRIS   → registrer et salg

Eksempel:
  python portfolio.py buy VWS 11 195.00
  python portfolio.py sell VWS 244.00
"""

import sys
import json
import time
import sqlite3
from pathlib import Path
from datetime import datetime, timezone

import yfinance as yf
from db import get_conn, DBConn

# ---------------------------------------------------------------------------
# Config — justér disse
# ---------------------------------------------------------------------------

CAPITAL        = 10_000  # DKK total kapital
MAX_POSITIONS  = 3       # maks åbne positioner ad gangen
STOP_LOSS_PCT  = 0.08    # sælg hvis -8% fra indgang
TAKE_PROFIT_PCT = 0.20   # sælg hvis +20% fra indgang
MIN_SIGNAL     = 0.35    # minimum signal_score for KØB
BUY_ANALYST    = {"stærk køb", "køb"}   # dansk label fra analyst_rec()

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

# ---------------------------------------------------------------------------
# DB setup
# ---------------------------------------------------------------------------

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


# ---------------------------------------------------------------------------
# Hjælpere
# ---------------------------------------------------------------------------

def _c25_map() -> dict:
    data = json.loads(C25_PATH.read_text())
    # c25.json er en dict med ticker som nøgle (plus _meta)
    return {k: v for k, v in data.items() if k != "_meta"}


def _current_price(ticker_yahoo: str) -> float | None:
    try:
        info = yf.Ticker(ticker_yahoo).fast_info
        return round(float(info.last_price), 2)
    except Exception:
        return None


def _analyst_label(ticker_yahoo: str) -> tuple[str, int]:
    """Returnerer (dansk label, antal analytikere)."""
    try:
        info = yf.Ticker(ticker_yahoo).info
        rec  = (info.get("recommendationKey") or "").lower()
        n    = info.get("numberOfAnalystOpinions") or 0
        label_map = {
            "strong_buy": "stærk køb", "buy": "køb",
            "hold": "hold", "sell": "sælg", "strong_sell": "stærk sælg",
        }
        return label_map.get(rec, rec or "ukendt"), int(n)
    except Exception:
        return "ukendt", 0


def _open_positions(db: DBConn) -> list:
    return db.execute("SELECT * FROM positions ORDER BY entry_date").fetchall()


def _open_count(db: DBConn) -> int:
    return db.execute("SELECT COUNT(*) AS cnt FROM positions").fetchone()["cnt"]


def _best_signals(db: DBConn) -> list:
    """Hent de bedste aktuelle signal-rækker (senest 7 dage)."""
    cutoff = int(time.time()) - 7 * 86400
    return db.execute("""
        SELECT ticker, MAX(company_name) AS company_name,
               MAX(signal_score)    AS signal_score,
               AVG(sentiment_score) AS sentiment_score,
               SUM(CASE WHEN sentiment = 'positive' THEN 1 ELSE 0 END) AS pos_count,
               SUM(CASE WHEN sentiment = 'negative' THEN 1 ELSE 0 END) AS neg_count,
               COUNT(*)             AS article_count,
               MAX(momentum_dir)    AS momentum_dir,
               MAX(claude_magnitude) AS magnitude
        FROM article_signals
        WHERE analyzed_at > ?
        GROUP BY ticker
        ORDER BY signal_score DESC
    """, (cutoff,)).fetchall()


# ---------------------------------------------------------------------------
# Ordre-logik
# ---------------------------------------------------------------------------

def _per_position(db: DBConn) -> int:
    open_n  = _open_count(db)
    slots   = MAX_POSITIONS - open_n
    if slots <= 0:
        return 0
    # fordel restkapi tal ligeligt
    used = sum(
        p["shares"] * p["entry_price"]
        for p in _open_positions(db)
    )
    remaining = max(0.0, CAPITAL - used)
    return int(remaining // slots)


def evaluate_orders(db: DBConn) -> None:
    c25       = _c25_map()
    signals   = _best_signals(db)
    open_pos  = {p["ticker"]: p for p in _open_positions(db)}
    open_n    = len(open_pos)
    per_pos   = _per_position(db)
    used      = sum(p["shares"] * p["entry_price"] for p in open_pos.values())

    print()
    print("═" * 66)
    print(f"  MONEYMAKER ORDRE-FORSLAG  ·  {datetime.now(timezone.utc).strftime('%d %b %Y  %H:%M')} UTC")
    print(f"  Kapital: {CAPITAL:,.0f} kr  │  Brugt: {used:,.0f} kr  │  "
          f"Åbne: {open_n}/{MAX_POSITIONS}  │  Pr. pos: {per_pos:,.0f} kr")
    print("═" * 66)

    buy_suggestions  = []
    hold_suggestions = []
    watch_list       = []

    for row in signals:
        ticker = row["ticker"]
        meta   = c25.get(ticker, {})
        yahoo  = meta.get("ticker_yahoo", f"{ticker}.CO")
        price  = _current_price(yahoo)
        if price is None:
            continue

        analyst_lbl, analyst_n = _analyst_label(yahoo)
        score  = row["signal_score"] or 0.0
        pos_ct = row["pos_count"] or 0
        neg_ct = row["neg_count"] or 0
        net_sentiment = pos_ct - neg_ct

        # Er vi allerede inde?
        if ticker in open_pos:
            hold_suggestions.append((ticker, row, meta, price, analyst_lbl, analyst_n))
            continue

        # KØB-betingelser
        analyst_ok = analyst_lbl in BUY_ANALYST
        signal_ok  = score >= MIN_SIGNAL
        enige      = (net_sentiment > 0 and analyst_ok) or (net_sentiment < 0 and analyst_lbl in {"sælg", "stærk sælg"})

        if signal_ok and analyst_ok and enige:
            buy_suggestions.append((ticker, row, meta, price, analyst_lbl, analyst_n, score))
        elif signal_ok or analyst_ok:
            watch_list.append((ticker, row, meta, price, analyst_lbl, analyst_n, score))

    # --- KØB ---
    if buy_suggestions and open_n < MAX_POSITIONS and per_pos > 0:
        print()
        print("  🟢 KØB-SIGNALER")
        print("  " + "─" * 62)
        slots_left = MAX_POSITIONS - open_n
        for (ticker, row, meta, price, analyst_lbl, analyst_n, score) in buy_suggestions[:slots_left]:
            shares = int(per_pos // price)
            if shares == 0:
                etps = meta.get("leveraged", [])
                etp_buy = next((e["ticker"] for e in etps if e.get("direction") == "long"), "")
                etp_txt = f"  → brug ETP {etp_buy}" if etp_buy else "  → pris for høj til direkte køb"
                print(f"  {ticker:<10}  {price:>7,.0f} kr/stk{etp_txt}")
                continue
            total      = shares * price
            stop       = round(price * (1 - STOP_LOSS_PCT), 2)
            take       = round(price * (1 + TAKE_PROFIT_PCT), 2)
            max_loss   = round(shares * (price - stop), 0)
            max_gain   = round(shares * (take - price), 0)
            print(f"  {ticker:<10}  KØB {shares} stk à {price:,.0f} kr  =  {total:,.0f} kr")
            print(f"             Stop-loss:   {stop:,.0f} kr  (max tab:   {max_loss:,.0f} kr)")
            print(f"             Take-profit: {take:,.0f} kr  (max gevinst: {max_gain:,.0f} kr)")
            print(f"             Signal: {score:.2f}  │  Analytikere ({analyst_n}): {analyst_lbl.upper()}")
            print(f"             Registrer:  python portfolio.py buy {ticker} {shares} {price}")
            print()
    elif not buy_suggestions:
        print()
        print("  Ingen klare KØB-signaler lige nu.")

    # --- ÅBNE POSITIONER (stop/take check) ---
    if open_pos:
        print()
        print("  📊 ÅBNE POSITIONER")
        print("  " + "─" * 62)
        for ticker, pos in open_pos.items():
            meta    = c25.get(ticker, {})
            yahoo   = meta.get("ticker_yahoo", f"{ticker}.CO")
            price   = _current_price(yahoo) or pos["entry_price"]
            entry   = pos["entry_price"]
            shares  = pos["shares"]
            pct_chg = (price - entry) / entry * 100
            value   = shares * price
            pnl     = shares * (price - entry)

            stop_hit  = price <= pos["stop_loss"]
            take_hit  = price >= pos["take_profit"]
            action    = "🔴 SÆLG (stop-loss!)" if stop_hit else ("🟡 SÆLG (take-profit!)" if take_hit else "⏳ HOLD")

            print(f"  {ticker:<10}  {shares:.0f} stk  │  ind: {entry:,.0f} kr  │  nu: {price:,.0f} kr  │  {pct_chg:+.1f}%")
            print(f"             Værdi: {value:,.0f} kr  │  P&L: {pnl:+,.0f} kr  │  {action}")
            print(f"             Stop: {pos['stop_loss']:,.0f} kr  │  Take: {pos['take_profit']:,.0f} kr")
            if stop_hit or take_hit:
                print(f"             Registrer:  python portfolio.py sell {ticker} {price:.2f}")
            print()

    # --- HOLD/WATCH ---
    if watch_list:
        print()
        print("  🔍 FØLG MED (signal endnu ikke klart nok)")
        print("  " + "─" * 62)
        for (ticker, row, meta, price, analyst_lbl, analyst_n, score) in watch_list[:4]:
            why = []
            if score < MIN_SIGNAL:
                why.append(f"signal {score:.2f} < {MIN_SIGNAL}")
            if analyst_lbl not in BUY_ANALYST:
                why.append(f"analytikere: {analyst_lbl.upper()}")
            print(f"  {ticker:<10}  {price:>7,.0f} kr  │  {', '.join(why)}")

    print()
    print("  Forklaring:")
    print("  Stop-loss    = sælg automatisk hvis kurs falder 10% fra dit køb")
    print("  Take-profit  = sælg hvis kurs stiger 25% — tag gevinsten hjem")
    print("  KØB-krav     = signal≥0.25 + analytikere siger KØB + begge peger samme vej")
    print()


# ---------------------------------------------------------------------------
# Portfolio CRUD
# ---------------------------------------------------------------------------

def cmd_buy(db: DBConn, args: list[str]) -> None:
    if len(args) < 3:
        print("Brug: python portfolio.py buy TICKER ANTAL PRIS")
        print("Eks:  python portfolio.py buy VWS 11 195.00")
        return
    ticker = args[0].upper()
    shares = float(args[1])
    price  = float(args[2])
    stop   = round(price * (1 - STOP_LOSS_PCT), 2)
    take   = round(price * (1 + TAKE_PROFIT_PCT), 2)
    today  = datetime.now(timezone.utc).strftime("%Y-%m-%d")
    total  = shares * price

    db.upsert(
        "positions", "ticker",
        ["ticker", "shares", "entry_price", "entry_date", "stop_loss", "take_profit"],
        (ticker, shares, price, today, stop, take),
    )
    db.execute("""
        INSERT INTO position_events
        (ticker, action, shares, price, total_dkk, event_date)
        VALUES (?, 'buy', ?, ?, ?, ?)
    """, (ticker, shares, price, total, today))
    db.commit()
    print(f"✅ KØB registreret: {shares:.0f} stk {ticker} à {price:.2f} kr = {total:.2f} kr")
    print(f"   Stop-loss:   {stop:.2f} kr")
    print(f"   Take-profit: {take:.2f} kr")


def cmd_sell(db: DBConn, args: list[str]) -> None:
    if len(args) < 2:
        print("Brug: python portfolio.py sell TICKER PRIS")
        print("Eks:  python portfolio.py sell VWS 244.00")
        return
    ticker = args[0].upper()
    price  = float(args[1])
    pos    = db.execute("SELECT * FROM positions WHERE ticker = ?", (ticker,)).fetchone()
    if not pos:
        print(f"❌ {ticker} — ingen åben position fundet")
        return
    shares  = pos["shares"]
    entry   = pos["entry_price"]
    pnl     = shares * (price - entry)
    total   = shares * price
    today   = datetime.now(timezone.utc).strftime("%Y-%m-%d")

    signal_correct = 1 if pnl > 0 else 0
    db.execute("DELETE FROM positions WHERE ticker = ?", (ticker,))
    db.execute("""
        INSERT INTO position_events
        (ticker, action, shares, price, total_dkk, pnl_dkk, signal_correct, event_date)
        VALUES (?, 'sell', ?, ?, ?, ?, ?, ?)
    """, (ticker, shares, price, total, pnl, signal_correct, today))
    db.commit()
    emoji = "🟢" if pnl >= 0 else "🔴"
    print(f"{emoji} SALG registreret: {shares:.0f} stk {ticker} à {price:.2f} kr = {total:.2f} kr")
    print(f"   Købt til: {entry:.2f} kr  │  P&L: {pnl:+.2f} kr  │  Signal: {'✅ korrekt' if signal_correct else '❌ forkert'}")


def cmd_status(db: DBConn) -> None:
    positions = _open_positions(db)
    history   = db.execute("""
        SELECT * FROM position_events ORDER BY event_date DESC LIMIT 10
    """).fetchall()

    if not positions:
        print("\n  Ingen åbne positioner.\n")
    else:
        print("\n  ÅBNE POSITIONER:")
        for p in positions:
            meta  = _c25_map().get(p["ticker"], {})
            yahoo = meta.get("ticker_yahoo", f"{p['ticker']}.CO")
            price = _current_price(yahoo) or p["entry_price"]
            pnl   = p["shares"] * (price - p["entry_price"])
            print(f"  {p['ticker']:<10} {p['shares']:.0f} stk  ind {p['entry_price']:.2f}  nu {price:.2f}  P&L {pnl:+.2f} kr")
            print(f"             Stop {p['stop_loss']:.2f}  Take {p['take_profit']:.2f}  (købt {p['entry_date']})")

    if history:
        print("\n  SENESTE HANDLER:")
        total_pnl = 0.0
        for e in history:
            pnl_txt = f"  P&L {e['pnl_dkk']:+.2f} kr" if e["pnl_dkk"] is not None else ""
            print(f"  {e['event_date']}  {e['action'].upper():<5} {e['ticker']:<10} "
                  f"{e['shares']:.0f} stk à {e['price']:.2f} kr{pnl_txt}")
            if e["pnl_dkk"] is not None:
                total_pnl += e["pnl_dkk"]
        sold = [e for e in history if e["action"] == "sell"]
        if sold:
            print(f"\n  Samlet realiseret P&L: {total_pnl:+.2f} kr")
    print()


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

def main() -> None:
    db   = get_db()
    args = sys.argv[1:]
    cmd  = args[0] if args else "orders"

    if cmd == "orders":
        evaluate_orders(db)
    elif cmd == "status":
        cmd_status(db)
    elif cmd == "buy" and len(args) >= 4:
        cmd_buy(db, args[1:])
    elif cmd == "sell" and len(args) >= 3:
        cmd_sell(db, args[1:])
    else:
        print(__doc__)

    db.close()


if __name__ == "__main__":
    main()
