import sqlite3
import os
import hashlib
import json
import datetime as dt
from typing import List, Dict, Any, Optional

from app.screener.schema import ensure_screener_schema

DB_PATH = os.environ.get("STOCK_DB_PATH", "/app/data/stock.db")

def _conn() -> sqlite3.Connection:
    db_path = os.environ.get("STOCK_DB_PATH", DB_PATH)
    parent = os.path.dirname(db_path)
    if parent:
        os.makedirs(parent, exist_ok=True)
    conn = sqlite3.connect(db_path, timeout=120.0)
    conn.row_factory = sqlite3.Row
    conn.execute("PRAGMA journal_mode=WAL")
    conn.execute("PRAGMA busy_timeout=120000")
    return conn

def init_db():
    with _conn() as conn:
        conn.execute("""
            CREATE TABLE IF NOT EXISTS articles (
                id INTEGER PRIMARY KEY AUTOINCREMENT,
                hash TEXT UNIQUE NOT NULL,
                category TEXT DEFAULT 'domestic',
                title TEXT NOT NULL,
                link TEXT,
                summary TEXT,
                press TEXT,
                pub_date TEXT,
                crawled_at TEXT
            )
        """)
        conn.execute("CREATE INDEX IF NOT EXISTS idx_articles_crawled ON articles(crawled_at DESC)")

        # 컬럼 추가 (기존 테이블 마이그레이션)
        cols = {r["name"] for r in conn.execute("PRAGMA table_info(articles)").fetchall()}
        if "category" not in cols:
            conn.execute("ALTER TABLE articles ADD COLUMN category TEXT DEFAULT 'domestic'")

        conn.execute("""
            CREATE TABLE IF NOT EXISTS portfolio (
                id INTEGER PRIMARY KEY AUTOINCREMENT,
                broker TEXT NOT NULL,
                ticker TEXT NOT NULL,
                name TEXT NOT NULL,
                quantity INTEGER NOT NULL,
                avg_price INTEGER NOT NULL,
                purchase_price INTEGER,
                created_at TEXT DEFAULT (datetime('now','localtime')),
                updated_at TEXT DEFAULT (datetime('now','localtime'))
            )
        """)

        # 마이그레이션: 기존 DB에 purchase_price 컬럼 없으면 추가 후 avg_price로 백필
        _pf_cols = {r["name"] for r in conn.execute("PRAGMA table_info(portfolio)").fetchall()}
        if "purchase_price" not in _pf_cols:
            conn.execute("ALTER TABLE portfolio ADD COLUMN purchase_price INTEGER")
            conn.execute("UPDATE portfolio SET purchase_price = avg_price WHERE purchase_price IS NULL")

        conn.execute("""
            CREATE TABLE IF NOT EXISTS broker_cash (
                id INTEGER PRIMARY KEY AUTOINCREMENT,
                broker TEXT UNIQUE NOT NULL,
                cash INTEGER NOT NULL DEFAULT 0,
                updated_at TEXT DEFAULT (datetime('now','localtime'))
            )
        """)

        conn.execute("""
            CREATE TABLE IF NOT EXISTS asset_snapshots (
                id INTEGER PRIMARY KEY AUTOINCREMENT,
                date TEXT UNIQUE NOT NULL,
                total_eval INTEGER NOT NULL,
                total_cash INTEGER NOT NULL,
                total_assets INTEGER NOT NULL,
                created_at TEXT DEFAULT (datetime('now','localtime'))
            )
        """)

        conn.execute("""
            CREATE TABLE IF NOT EXISTS sell_history (
                id INTEGER PRIMARY KEY AUTOINCREMENT,
                broker TEXT NOT NULL,
                ticker TEXT NOT NULL,
                name TEXT NOT NULL,
                quantity INTEGER NOT NULL,
                avg_price REAL NOT NULL,
                sell_price REAL NOT NULL,
                commission REAL NOT NULL DEFAULT 0,
                buy_amount REAL NOT NULL,
                sell_amount REAL NOT NULL,
                realized_profit REAL NOT NULL,
                realized_rate REAL NOT NULL,
                sold_at TEXT NOT NULL
            )
        """)

        # sell_history 마이그레이션: commission 컬럼 추가
        sh_cols = {r["name"] for r in conn.execute("PRAGMA table_info(sell_history)").fetchall()}
        if "commission" not in sh_cols:
            conn.execute("ALTER TABLE sell_history ADD COLUMN commission REAL NOT NULL DEFAULT 0")

        conn.execute(
            """
            CREATE TABLE IF NOT EXISTS holdings_signals (
              date        TEXT NOT NULL,
              ticker      TEXT NOT NULL,
              name        TEXT,
              action      TEXT NOT NULL,
              tech_score  REAL,
              exit_flags  TEXT NOT NULL DEFAULT '{}',
              issues      TEXT NOT NULL DEFAULT '[]',
              close       INTEGER,
              pnl_rate    REAL,
              reasons     TEXT,
              created_at  TEXT NOT NULL DEFAULT (datetime('now','localtime')),
              PRIMARY KEY (date, ticker)
            );
            """
        )
        conn.execute("CREATE INDEX IF NOT EXISTS idx_holdings_sig_ticker "
                     "ON holdings_signals(ticker, date DESC);")

        # 실시간 매매 알람: watchlist / alert_state / alert_history
        conn.execute("""
            CREATE TABLE IF NOT EXISTS watchlist (
                ticker      TEXT PRIMARY KEY,
                name        TEXT,
                note        TEXT,
                params_json TEXT NOT NULL DEFAULT '{}',
                added_at    TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ','now'))
            )
        """)
        conn.execute("""
            CREATE TABLE IF NOT EXISTS trade_alert_state (
                ticker           TEXT NOT NULL,
                kind             TEXT NOT NULL,
                condition        TEXT NOT NULL,
                currently_firing INTEGER NOT NULL DEFAULT 0,
                first_fired_at   TEXT,
                last_fired_at    TEXT,
                last_seen_at     TEXT,
                PRIMARY KEY (ticker, kind, condition)
            )
        """)
        conn.execute("""
            CREATE TABLE IF NOT EXISTS trade_alert_history (
                id          INTEGER PRIMARY KEY AUTOINCREMENT,
                ticker      TEXT NOT NULL,
                name        TEXT,
                kind        TEXT NOT NULL,
                condition   TEXT NOT NULL,
                price       REAL,
                detail_json TEXT,
                fired_at    TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ','now'))
            )
        """)
        conn.execute("CREATE INDEX IF NOT EXISTS idx_tah_fired ON trade_alert_history(fired_at DESC)")

        # Screener 스키마 부트스트랩 (7테이블 + 디폴트 설정 시드)
        ensure_screener_schema(conn)

def save_articles(articles: List[Dict[str, str]]) -> int:
    count = 0
    with _conn() as conn:
        for a in articles:
            # 중복 체크용 해시 (제목+링크)
            unique_str = f"{a['title']}|{a['link']}"
            h = hashlib.md5(unique_str.encode()).hexdigest()
            
            try:
                cat = a.get("category", "domestic")
                conn.execute("""
                    INSERT INTO articles (hash, category, title, link, summary, press, pub_date, crawled_at)
                    VALUES (?, ?, ?, ?, ?, ?, ?, ?)
                """, (h, cat, a['title'], a['link'], a['summary'], a['press'], a['date'], a['crawled_at']))
                count += 1
            except sqlite3.IntegrityError:
                pass # 이미 존재함
    return count

def get_latest_articles(limit: int = 20, category: str = None) -> List[Dict[str, Any]]:
    with _conn() as conn:
        if category:
            rows = conn.execute(
                "SELECT * FROM articles WHERE category = ? ORDER BY crawled_at DESC, id DESC LIMIT ?",
                (category, limit)
            ).fetchall()
        else:
            rows = conn.execute(
                "SELECT * FROM articles ORDER BY crawled_at DESC, id DESC LIMIT ?",
                (limit,)
            ).fetchall()
        return [dict(r) for r in rows]


# --- Portfolio CRUD ---

def add_portfolio_item(
    broker: str, ticker: str, name: str, quantity: int, avg_price: int,
    purchase_price: Optional[int] = None,
) -> int:
    # purchase_price 미입력 시 avg_price로 기본값 설정 (하위호환)
    if purchase_price is None:
        purchase_price = avg_price
    with _conn() as conn:
        cur = conn.execute(
            "INSERT INTO portfolio (broker, ticker, name, quantity, avg_price, purchase_price) VALUES (?, ?, ?, ?, ?, ?)",
            (broker, ticker, name, quantity, avg_price, purchase_price),
        )
        return cur.lastrowid


def get_all_portfolio() -> List[Dict[str, Any]]:
    with _conn() as conn:
        rows = conn.execute("SELECT * FROM portfolio ORDER BY id").fetchall()
        return [dict(r) for r in rows]


def get_portfolio_item(item_id: int) -> Dict[str, Any] | None:
    with _conn() as conn:
        row = conn.execute("SELECT * FROM portfolio WHERE id = ?", (item_id,)).fetchone()
        return dict(row) if row else None


def update_portfolio_item(item_id: int, **kwargs) -> bool:
    allowed = {"broker", "ticker", "name", "quantity", "avg_price", "purchase_price"}
    fields = {k: v for k, v in kwargs.items() if k in allowed and v is not None}
    if not fields:
        return False
    fields["updated_at"] = __import__("datetime").datetime.now().strftime("%Y-%m-%d %H:%M:%S")
    set_clause = ", ".join(f"{k} = ?" for k in fields)
    values = list(fields.values()) + [item_id]
    with _conn() as conn:
        cur = conn.execute(f"UPDATE portfolio SET {set_clause} WHERE id = ?", values)
        return cur.rowcount > 0


def delete_portfolio_item(item_id: int) -> bool:
    with _conn() as conn:
        cur = conn.execute("DELETE FROM portfolio WHERE id = ?", (item_id,))
        return cur.rowcount > 0


# --- Broker Cash CRUD ---

def upsert_broker_cash(broker: str, cash: int) -> None:
    now = __import__("datetime").datetime.now().strftime("%Y-%m-%d %H:%M:%S")
    with _conn() as conn:
        conn.execute("""
            INSERT INTO broker_cash (broker, cash, updated_at)
            VALUES (?, ?, ?)
            ON CONFLICT(broker) DO UPDATE SET cash = excluded.cash, updated_at = excluded.updated_at
        """, (broker, cash, now))


def get_all_broker_cash() -> List[Dict[str, Any]]:
    with _conn() as conn:
        rows = conn.execute("SELECT * FROM broker_cash ORDER BY broker").fetchall()
        return [dict(r) for r in rows]


def delete_broker_cash(broker: str) -> bool:
    with _conn() as conn:
        cur = conn.execute("DELETE FROM broker_cash WHERE broker = ?", (broker,))
        return cur.rowcount > 0


# --- Asset Snapshot CRUD ---

def upsert_asset_snapshot(date: str, total_eval: int, total_cash: int, total_assets: int) -> None:
    now = __import__("datetime").datetime.now().strftime("%Y-%m-%d %H:%M:%S")
    with _conn() as conn:
        conn.execute("""
            INSERT INTO asset_snapshots (date, total_eval, total_cash, total_assets, created_at)
            VALUES (?, ?, ?, ?, ?)
            ON CONFLICT(date) DO UPDATE SET
                total_eval = excluded.total_eval,
                total_cash = excluded.total_cash,
                total_assets = excluded.total_assets,
                created_at = excluded.created_at
        """, (date, total_eval, total_cash, total_assets, now))


# --- Sell History CRUD ---

def add_sell_history(data: Dict[str, Any]) -> Dict[str, Any]:
    with _conn() as conn:
        cur = conn.execute("""
            INSERT INTO sell_history
                (broker, ticker, name, quantity, avg_price, sell_price,
                 commission, buy_amount, sell_amount, realized_profit, realized_rate, sold_at)
            VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
        """, (
            data["broker"], data["ticker"], data["name"], data["quantity"],
            data["avg_price"], data["sell_price"], data.get("commission", 0),
            data["buy_amount"], data["sell_amount"], data["realized_profit"],
            data["realized_rate"], data["sold_at"],
        ))
        row = conn.execute("SELECT * FROM sell_history WHERE id = ?", (cur.lastrowid,)).fetchone()
        return dict(row)


def get_sell_history(broker: str = None, days: int = None) -> List[Dict[str, Any]]:
    conditions = []
    params = []
    if broker:
        conditions.append("broker = ?")
        params.append(broker)
    if days:
        conditions.append("sold_at >= datetime('now', ? || ' days')")
        params.append(f"-{days}")
    where = f"WHERE {' AND '.join(conditions)}" if conditions else ""
    with _conn() as conn:
        rows = conn.execute(
            f"SELECT * FROM sell_history {where} ORDER BY sold_at DESC",
            params,
        ).fetchall()
        return [dict(r) for r in rows]


def update_sell_history(record_id: int, data: Dict[str, Any]) -> Dict[str, Any] | None:
    fields = ["broker", "ticker", "name", "quantity", "avg_price", "sell_price",
              "commission", "buy_amount", "sell_amount", "realized_profit", "realized_rate", "sold_at"]
    set_clause = ", ".join(f"{f} = ?" for f in fields)
    values = [data.get(f, 0) if f == "commission" else data[f] for f in fields] + [record_id]
    with _conn() as conn:
        cur = conn.execute(f"UPDATE sell_history SET {set_clause} WHERE id = ?", values)
        if cur.rowcount == 0:
            return None
        row = conn.execute("SELECT * FROM sell_history WHERE id = ?", (record_id,)).fetchone()
        return dict(row)


def delete_sell_history(record_id: int) -> bool:
    with _conn() as conn:
        cur = conn.execute("DELETE FROM sell_history WHERE id = ?", (record_id,))
        return cur.rowcount > 0


def get_asset_snapshots(days: int = 30) -> List[Dict[str, Any]]:
    with _conn() as conn:
        if days == 0:
            rows = conn.execute(
                "SELECT date, total_eval, total_cash, total_assets FROM asset_snapshots ORDER BY date ASC"
            ).fetchall()
        else:
            rows = conn.execute(
                "SELECT date, total_eval, total_cash, total_assets FROM asset_snapshots ORDER BY date DESC LIMIT ?",
                (days,)
            ).fetchall()
            rows = list(reversed(rows))
        return [dict(r) for r in rows]


# --- KRX Master ---

def get_krx_tickers() -> set:
    with _conn() as conn:
        try:
            rows = conn.execute("SELECT ticker FROM krx_master").fetchall()
        except Exception:
            return set()
    return {r["ticker"] for r in rows}


# --- Holdings Signals CRUD ---

def upsert_holdings_signal(
    date: str, ticker: str, name: Optional[str], action: str,
    tech_score: Optional[float], exit_flags: dict, issues: list,
    close: Optional[int], pnl_rate: Optional[float], reasons: Optional[str],
) -> None:
    with _conn() as conn:
        conn.execute(
            """
            INSERT INTO holdings_signals
              (date, ticker, name, action, tech_score, exit_flags, issues, close, pnl_rate, reasons)
            VALUES (?,?,?,?,?,?,?,?,?,?)
            ON CONFLICT(date, ticker) DO UPDATE SET
              name=excluded.name, action=excluded.action, tech_score=excluded.tech_score,
              exit_flags=excluded.exit_flags, issues=excluded.issues, close=excluded.close,
              pnl_rate=excluded.pnl_rate, reasons=excluded.reasons
            """,
            (date, ticker, name, action, tech_score,
             json.dumps(exit_flags, ensure_ascii=False),
             json.dumps(issues, ensure_ascii=False), close, pnl_rate, reasons),
        )

def _row_to_signal(r) -> dict:
    d = dict(r)
    d["exit_flags"] = json.loads(d.get("exit_flags") or "{}")
    d["issues"] = json.loads(d.get("issues") or "[]")
    return d

def get_holdings_signals(date: str) -> list:
    with _conn() as conn:
        rows = conn.execute(
            "SELECT * FROM holdings_signals WHERE date=? ORDER BY ticker", (date,)).fetchall()
    return [_row_to_signal(r) for r in rows]

def get_latest_holdings_date() -> str | None:
    with _conn() as conn:
        r = conn.execute("SELECT MAX(date) AS d FROM holdings_signals").fetchone()
    return r["d"] if r and r["d"] else None

def get_holdings_signal_history(ticker: str, limit: int = 30) -> list:
    """최근 N개 시그널 행 (시그널은 거래일당 1행이라 ≈ N 거래일)."""
    with _conn() as conn:
        rows = conn.execute(
            "SELECT * FROM holdings_signals WHERE ticker=? ORDER BY date DESC LIMIT ?",
            (ticker, limit)).fetchall()
    return [_row_to_signal(r) for r in rows]


# --- 실시간 매매 알람: 공통 유틸 ---

def _now_iso() -> str:
    return dt.datetime.utcnow().strftime("%Y-%m-%dT%H:%M:%fZ")


# --- Watchlist CRUD ---

def add_watchlist(ticker: str, name: str = None, note: str = None) -> None:
    with _conn() as conn:
        conn.execute(
            "INSERT OR IGNORE INTO watchlist(ticker,name,note) VALUES(?,?,?)",
            (ticker, name, note),
        )
        # 이름/노트 갱신(이미 있으면)
        conn.execute(
            "UPDATE watchlist SET name=COALESCE(?,name), note=COALESCE(?,note) WHERE ticker=?",
            (name, note, ticker),
        )


def remove_watchlist(ticker: str) -> bool:
    with _conn() as conn:
        cur = conn.execute("DELETE FROM watchlist WHERE ticker=?", (ticker,))
        return cur.rowcount > 0


def get_watchlist() -> list:
    with _conn() as conn:
        rows = conn.execute("SELECT * FROM watchlist ORDER BY added_at").fetchall()
    return [
        {"ticker": r["ticker"], "name": r["name"], "note": r["note"],
         "params": json.loads(r["params_json"] or "{}"), "added_at": r["added_at"]}
        for r in rows
    ]


# --- Trade Alert State ---

def get_alert_state_firing() -> set:
    with _conn() as conn:
        rows = conn.execute(
            "SELECT ticker,kind,condition FROM trade_alert_state WHERE currently_firing=1"
        ).fetchall()
    return {(r["ticker"], r["kind"], r["condition"]) for r in rows}


def set_alert_firing(ticker: str, kind: str, condition: str, firing: bool,
                     at_iso: str = None, mark_fired: bool = True) -> None:
    """currently_firing 상태 갱신.

    mark_fired=True(기본): 실제 알림 발송 → first/last_fired_at 갱신.
    mark_fired=False: 쿨다운으로 발송 억제하되 firing 상태만 유지 → 발동시각 미갱신
      (쿨다운이 계속 연장되지 않도록).
    """
    now = at_iso or _now_iso()
    with _conn() as conn:
        if firing and mark_fired:
            conn.execute(
                """INSERT INTO trade_alert_state(ticker,kind,condition,currently_firing,first_fired_at,last_fired_at,last_seen_at)
                   VALUES(?,?,?,1,?,?,?)
                   ON CONFLICT(ticker,kind,condition) DO UPDATE SET
                     currently_firing=1,
                     first_fired_at=COALESCE(first_fired_at,excluded.first_fired_at),
                     last_fired_at=excluded.last_fired_at,
                     last_seen_at=excluded.last_seen_at""",
                (ticker, kind, condition, now, now, now),
            )
        elif firing and not mark_fired:
            conn.execute(
                """INSERT INTO trade_alert_state(ticker,kind,condition,currently_firing,last_seen_at)
                   VALUES(?,?,?,1,?)
                   ON CONFLICT(ticker,kind,condition) DO UPDATE SET
                     currently_firing=1, last_seen_at=excluded.last_seen_at""",
                (ticker, kind, condition, now),
            )
        else:
            conn.execute(
                "UPDATE trade_alert_state SET currently_firing=0, last_seen_at=? WHERE ticker=? AND kind=? AND condition=?",
                (now, ticker, kind, condition),
            )


def get_alert_last_fired_map() -> dict:
    """{(ticker,kind,condition): last_fired_at ISO} — 쿨다운 판정용."""
    with _conn() as conn:
        rows = conn.execute(
            "SELECT ticker,kind,condition,last_fired_at FROM trade_alert_state"
        ).fetchall()
    return {(r["ticker"], r["kind"], r["condition"]): r["last_fired_at"] for r in rows}


def get_ticker_name(ticker: str) -> Optional[str]:
    """종목명 해석 — watchlist → portfolio → krx_master 순. 없으면 None."""
    with _conn() as conn:
        for sql in (
            "SELECT name FROM watchlist WHERE ticker=?",
            "SELECT name FROM portfolio WHERE ticker=? LIMIT 1",
            "SELECT name FROM krx_master WHERE ticker=?",
        ):
            try:
                row = conn.execute(sql, (ticker,)).fetchone()
            except sqlite3.OperationalError:
                continue  # 일부 테스트 DB엔 해당 테이블 부재
            if row and row["name"]:
                return row["name"]
    return None


def touch_alert_seen(keys: list, at_iso: str) -> None:
    with _conn() as conn:
        for (ticker, kind, condition) in keys:
            conn.execute(
                "UPDATE trade_alert_state SET last_seen_at=? WHERE ticker=? AND kind=? AND condition=?",
                (at_iso, ticker, kind, condition),
            )


# --- Trade Alert History ---

def add_alert_history(ticker: str, name: str, kind: str, condition: str, price, detail: dict) -> int:
    with _conn() as conn:
        cur = conn.execute(
            "INSERT INTO trade_alert_history(ticker,name,kind,condition,price,detail_json) VALUES(?,?,?,?,?,?)",
            (ticker, name, kind, condition, price, json.dumps(detail or {}, ensure_ascii=False)),
        )
        return cur.lastrowid


def get_alert_history(days: int = 7) -> list:
    with _conn() as conn:
        rows = conn.execute(
            "SELECT * FROM trade_alert_history WHERE fired_at >= strftime('%Y-%m-%dT%H:%M:%fZ','now', ?) ORDER BY fired_at DESC",
            (f"-{int(days)} days",),
        ).fetchall()
    return [
        {"id": r["id"], "ticker": r["ticker"], "name": r["name"], "kind": r["kind"],
         "condition": r["condition"], "price": r["price"],
         "detail": json.loads(r["detail_json"] or "{}"), "fired_at": r["fired_at"]}
        for r in rows
    ]
