"""
Beast Trader API — 市场结构、信号诊断、交易监测数据
"""
import os
import time
import hmac
import hashlib
import json
import sqlite3
import ccxt
import pandas as pd
import numpy as np
from datetime import datetime, timezone, timedelta
from typing import Optional
from fastapi import FastAPI, HTTPException, Request, Query
from fastapi.middleware.cors import CORSMiddleware
from fastapi.responses import FileResponse
from pydantic import BaseModel
import uvicorn

from indicators import compute_all_indicators

# =============================================
# 配置
# =============================================
API_KEY = os.environ.get("BEAST_API_KEY", "beast2025")
BINANCE_RATE_LIMIT_DELAY = 1.0  # 秒
DATA_CACHE_SECONDS = 30  # 缓存时间

# Freqtrade 数据库路径（只读）
FREQTRADE_DB = os.environ.get(
    "FREQTRADE_DB",
    os.path.expanduser("~/freqtrade/user_data/tradesv3.sqlite")
)
INITIAL_WALLET = float(os.environ.get("INITIAL_WALLET", 10000))

app = FastAPI(title="Beast Trader API", version="1.0")

app.add_middleware(
    CORSMiddleware,
    allow_origins=["*"],
    allow_credentials=True,
    allow_methods=["*"],
    allow_headers=["*"],
)

# =============================================
# 缓存
# =============================================
_cache = {"data": None, "timestamp": 0}

# =============================================
# 认证中间件
# =============================================
@app.middleware("http")
async def auth_middleware(request: Request, call_next):
    if request.method == "OPTIONS" or request.url.path in ["/api/health", "/", "/index.html"]:
        return await call_next(request)
    
    auth = request.headers.get("X-API-Key")
    if not auth or auth != API_KEY:
        raise HTTPException(status_code=401, detail="Invalid API Key")
    return await call_next(request)


# =============================================
# 数据获取
# =============================================
def fetch_binance_data():
    """获取 BTC 和 ETH 的 D1/4H/1H K线数据 + 实时ticker"""
    exchange = ccxt.binance({
        "enableRateLimit": True,
    })
    
    now = exchange.milliseconds()
    since_1d = now - 180 * 24 * 60 * 60 * 1000  # 180天 D1
    since_4h = now - 60 * 24 * 60 * 60 * 1000   # 60天 4H
    since_1h = now - 14 * 24 * 60 * 60 * 1000   # 14天 1H

    result = {}

    for symbol in ["ETH/USDT", "BTC/USDT"]:
        try:
            ohlcv_1d = exchange.fetch_ohlcv(symbol, "1d", since_1d, 200)
            ohlcv_4h = exchange.fetch_ohlcv(symbol, "4h", since_4h, 400)
            ohlcv_1h = exchange.fetch_ohlcv(symbol, "1h", since_1h, 400)
            ticker = exchange.fetch_ticker(symbol)

            df_1d = pd.DataFrame(ohlcv_1d, columns=["timestamp", "open", "high", "low", "close", "volume"])
            df_1d["timestamp"] = pd.to_datetime(df_1d["timestamp"], unit="ms")
            df_1d.set_index("timestamp", inplace=True)

            df_4h = pd.DataFrame(ohlcv_4h, columns=["timestamp", "open", "high", "low", "close", "volume"])
            df_4h["timestamp"] = pd.to_datetime(df_4h["timestamp"], unit="ms")
            df_4h.set_index("timestamp", inplace=True)

            df_1h = pd.DataFrame(ohlcv_1h, columns=["timestamp", "open", "high", "low", "close", "volume"])
            df_1h["timestamp"] = pd.to_datetime(df_1h["timestamp"], unit="ms")
            df_1h.set_index("timestamp", inplace=True)

            result[symbol] = {"1d": df_1d, "4h": df_4h, "1h": df_1h, "ticker": ticker}
            time.sleep(0.5)  # rate limit
        except Exception as e:
            print(f"Error fetching {symbol}: {e}")

    return result


def get_cached_data():
    """获取缓存数据或刷新"""
    now = time.time()
    if now - _cache["timestamp"] < DATA_CACHE_SECONDS and _cache["data"] is not None:
        return _cache["data"]
    
    raw = fetch_binance_data()
    result = {}
    for symbol, frames in raw.items():
        try:
            computed = compute_all_indicators(frames["1h"], frames["4h"], frames["1d"])
            # 用实时ticker覆盖current_price（指标里用的是1H open，不实时）
            price = frames.get("ticker", {}).get("last")
            if price is not None:
                computed["current_price"] = round(float(price), 2)
            result[symbol] = computed
            # 附加最近K线数据用于图表
            result[symbol]["price_history"] = {
                "1h": {
                    "timestamps": frames["1h"].index[-48:].strftime("%m-%d %H:%M").tolist(),
                    "close": [round(float(x), 2) for x in frames["1h"]["close"].iloc[-48:].tolist()],
                }
            }
        except Exception as e:
            print(f"Error computing {symbol}: {e}")
            result[symbol] = {"error": str(e)}
    
    _cache["data"] = result
    _cache["timestamp"] = now
    return result


# =============================================
# API 路由
# =============================================

@app.get("/api/health")
def health():
    return {"status": "ok", "time": datetime.now(timezone.utc).isoformat()}


@app.get("/api/market-structure")
def market_structure():
    """
    市场结构分析：S/R 位、Swing Point、趋势方向、供需区
    """
    data = get_cached_data()
    eth = data.get("ETH/USDT", {})
    btc = data.get("BTC/USDT", {})
    
    if "error" in eth:
        raise HTTPException(status_code=500, detail=eth["error"])
    
    return {
        "eth": {
            "current_price": eth.get("current_price"),
            "support": eth.get("diagnosis", {}).get("support"),
            "resistance": eth.get("diagnosis", {}).get("resistance"),
            "zone_width_pct": eth.get("diagnosis", {}).get("zone_width_pct"),
            "price_position_pct": eth.get("diagnosis", {}).get("price_position_in_zone"),
            "trend_1d": eth.get("trend_1d"),
            "trend_strength_4h": eth.get("trend_strength_4h"),
            "swing_points": eth.get("swing_points"),
            "price_history_1h": eth.get("price_history", {}).get("1h"),
        },
        "btc": {
            "current_price": btc.get("current_price") if "error" not in btc else None,
            "support_4h": btc.get("diagnosis", {}).get("support_4h"),
            "resistance_4h": btc.get("diagnosis", {}).get("resistance_4h"),
            "trend_1d": btc.get("trend_1d"),
        },
        "cached_at": datetime.now(timezone.utc).isoformat(),
    }


@app.get("/api/signal-diagnosis")
def signal_diagnosis():
    """
    信号诊断：8 层过滤条件逐一检查
    """
    data = get_cached_data()
    eth = data.get("ETH/USDT", {})
    btc = data.get("BTC/USDT", {})
    
    if "error" in eth:
        raise HTTPException(status_code=500, detail=eth["error"])
    
    return {
        "eth": {
            "can_enter_long": eth.get("can_enter_long"),
            "can_enter_short": eth.get("can_enter_short"),
            "diagnosis": eth.get("diagnosis"),
            "filters": eth.get("filters"),
            "candle_1h": eth.get("candle_1h"),
            "trend_strength_4h": eth.get("trend_strength_4h"),
        },
        "btc": {
            "can_enter_long": btc.get("can_enter_long"),
            "can_enter_short": btc.get("can_enter_short"),
            "diagnosis": btc.get("diagnosis"),
            "filters": btc.get("filters"),
        } if "error" not in btc else {"error": btc.get("error")},
        "cached_at": datetime.now(timezone.utc).isoformat(),
    }


# =============================================
# 静态文件服务（前端）
# =============================================
FRONTEND_DIR = os.path.join(os.path.dirname(__file__), "..", "frontend")

@app.get("/")
def serve_index():
    return FileResponse(os.path.join(FRONTEND_DIR, "index.html"))


# =============================================
# 交易监测 API（读取 freqtrade SQLite 数据库）
# =============================================

def _get_db():
    """获取只读数据库连接"""
    conn = sqlite3.connect(FREQTRADE_DB)
    conn.row_factory = sqlite3.Row
    return conn


def _row_to_dict(row):
    """将 sqlite3.Row 转为 dict，处理 datetime 和 boolean"""
    if row is None:
        return None
    d = {}
    for k in row.keys():
        v = row[k]
        if isinstance(v, bytes):
            d[k] = v.decode("utf-8", errors="replace")
        elif hasattr(v, "isoformat"):
            d[k] = v.isoformat() if v else None
        else:
            d[k] = v
    return d


@app.get("/api/account")
def account_overview():
    """
    账户概览：当前余额、盈亏统计、策略信息
    数据来源：freqtrade SQLite 数据库 (wallet_history + trades)
    """
    try:
        conn = _get_db()
        
        # 最新钱包余额
        wallet = conn.execute(
            "SELECT * FROM wallet_history ORDER BY timestamp DESC LIMIT 1"
        ).fetchone()
        
        # 交易统计
        stats = conn.execute("""
            SELECT 
                COUNT(*) as total_trades,
                SUM(CASE WHEN is_open = 1 THEN 1 ELSE 0 END) as open_trades,
                SUM(CASE WHEN is_open = 0 THEN 1 ELSE 0 END) as closed_trades,
                SUM(CASE WHEN is_open = 0 AND close_profit > 0 THEN 1 ELSE 0 END) as winning_trades,
                SUM(CASE WHEN is_open = 0 AND close_profit <= 0 THEN 1 ELSE 0 END) as losing_trades,
                ROUND(SUM(close_profit_abs), 8) as total_profit_abs,
                ROUND(AVG(CASE WHEN is_open = 0 THEN close_profit ELSE NULL END), 6) as avg_profit_pct,
                ROUND(MAX(CASE WHEN is_open = 0 THEN close_profit ELSE NULL END), 6) as best_trade_pct,
                ROUND(MIN(CASE WHEN is_open = 0 THEN close_profit ELSE NULL END), 6) as worst_trade_pct
            FROM trades
        """).fetchone()
        
        # 当前持仓详情
        open_positions = conn.execute("""
            SELECT id, pair, is_short, open_rate, stake_amount, amount,
                   open_date, stop_loss, max_rate, min_rate,
                   ROUND((open_rate - ?) / open_rate * 100, 4) as unrealized_pnl_pct
            FROM trades
            WHERE is_open = 1
            ORDER BY open_date DESC
        """, [0]).fetchall()  # placeholder, real current price handled in response
        
        # 策略名称
        strategy_row = conn.execute(
            "SELECT DISTINCT strategy FROM trades WHERE strategy IS NOT NULL ORDER BY id DESC LIMIT 1"
        ).fetchone()
        strategy_name = strategy_row["strategy"] if strategy_row else "v2.2c"
        
        # 近期每日盈亏（近30天）
        daily_pnl = conn.execute("""
            SELECT DATE(close_date) as day,
                   ROUND(SUM(close_profit_abs), 8) as pnl_abs,
                   COUNT(*) as trade_count,
                   SUM(CASE WHEN close_profit > 0 THEN 1 ELSE 0 END) as wins
            FROM trades
            WHERE is_open = 0 AND close_date IS NOT NULL
            GROUP BY DATE(close_date)
            ORDER BY day DESC
            LIMIT 30
        """).fetchall()
        
        conn.close()
        
        # 计算当前余额
        if wallet:
            current_balance = wallet["total_quote"] or wallet["balance"]
            last_update = _row_to_dict(wallet)["timestamp"]
        else:
            current_balance = INITIAL_WALLET
            last_update = None
        
        # 计算总收益
        total_pnl_pct = round((current_balance - INITIAL_WALLET) / INITIAL_WALLET * 100, 2) if current_balance else 0
        
        # 胜率
        closed = stats["closed_trades"] or 0
        wins = stats["winning_trades"] or 0
        win_rate = round(wins / closed * 100, 1) if closed > 0 else 0
        
        return {
            "account": {
                "initial_wallet": INITIAL_WALLET,
                "current_balance": round(current_balance, 2),
                "total_pnl_pct": total_pnl_pct,
                "total_pnl_abs": round(current_balance - INITIAL_WALLET, 2),
                "last_update": last_update,
                "strategy": strategy_name,
            },
            "stats": {
                "total_trades": stats["total_trades"] or 0,
                "open_trades": stats["open_trades"] or 0,
                "closed_trades": closed,
                "win_rate": win_rate,
                "wins": wins,
                "losses": stats["losing_trades"] or 0,
                "avg_profit_pct": stats["avg_profit_pct"],
                "best_trade_pct": stats["best_trade_pct"],
                "worst_trade_pct": stats["worst_trade_pct"],
                "total_profit_abs": stats["total_profit_abs"] or 0,
            },
            "open_positions": [_row_to_dict(r) for r in open_positions],
            "daily_pnl": [_row_to_dict(r) for r in daily_pnl],
            "cached_at": datetime.now(timezone.utc).isoformat(),
        }
    except Exception as e:
        raise HTTPException(status_code=500, detail=f"Database error: {str(e)}")


@app.get("/api/trades")
def trade_list(
    limit: int = Query(default=30, ge=1, le=200),
    offset: int = Query(default=0, ge=0),
    status: str = Query(default="all", pattern="^(all|open|closed)$"),
    sort: str = Query(default="desc", pattern="^(asc|desc)$"),
):
    """
    交易列表：支持分页和状态筛选
    """
    try:
        conn = _get_db()
        
        where_clause = ""
        if status == "open":
            where_clause = "WHERE is_open = 1"
        elif status == "closed":
            where_clause = "WHERE is_open = 0"
        
        order = "DESC" if sort == "desc" else "ASC"
        
        rows = conn.execute(f"""
            SELECT id, pair, is_open, is_short, open_rate, close_rate,
                   close_profit, close_profit_abs, stake_amount, amount,
                   open_date, close_date, exit_reason, strategy,
                   max_rate, min_rate, stop_loss, initial_stop_loss,
                   enter_tag, leverage
            FROM trades
            {where_clause}
            ORDER BY open_date {order}
            LIMIT ? OFFSET ?
        """, [limit, offset]).fetchall()
        
        total = conn.execute(f"SELECT COUNT(*) as cnt FROM trades {where_clause}").fetchone()
        
        conn.close()
        
        return {
            "trades": [_row_to_dict(r) for r in rows],
            "pagination": {
                "total": total["cnt"],
                "limit": limit,
                "offset": offset,
                "has_more": offset + limit < total["cnt"],
            },
            "cached_at": datetime.now(timezone.utc).isoformat(),
        }
    except Exception as e:
        raise HTTPException(status_code=500, detail=f"Database error: {str(e)}")


@app.get("/api/trades/stats")
def trade_stats():
    """
    交易统计：按方向/时间/策略维度的聚合数据
    """
    try:
        conn = _get_db()
        
        # 按方向
        by_direction = conn.execute("""
            SELECT 
                CASE WHEN is_short = 1 THEN 'SHORT' ELSE 'LONG' END as direction,
                COUNT(*) as total,
                SUM(CASE WHEN is_open = 0 AND close_profit > 0 THEN 1 ELSE 0 END) as wins,
                SUM(CASE WHEN is_open = 0 AND close_profit <= 0 THEN 1 ELSE 0 END) as losses,
                ROUND(AVG(CASE WHEN is_open = 0 THEN close_profit ELSE NULL END), 6) as avg_profit_pct,
                ROUND(SUM(CASE WHEN is_open = 0 THEN close_profit_abs ELSE 0 END), 8) as total_pnl_abs
            FROM trades
            WHERE is_open = 0
            GROUP BY is_short
        """).fetchall()
        
        # 按退出原因
        by_exit = conn.execute("""
            SELECT 
                exit_reason,
                COUNT(*) as count,
                ROUND(AVG(close_profit), 6) as avg_profit_pct,
                ROUND(SUM(close_profit_abs), 8) as total_pnl_abs
            FROM trades
            WHERE is_open = 0 AND exit_reason IS NOT NULL
            GROUP BY exit_reason
            ORDER BY count DESC
            LIMIT 10
        """).fetchall()
        
        # 月度表现
        monthly = conn.execute("""
            SELECT 
                strftime('%Y-%m', open_date) as month,
                COUNT(*) as total,
                SUM(CASE WHEN close_profit > 0 THEN 1 ELSE 0 END) as wins,
                ROUND(AVG(close_profit), 6) as avg_profit_pct,
                ROUND(SUM(close_profit_abs), 8) as total_pnl_abs
            FROM trades
            WHERE is_open = 0
            GROUP BY strftime('%Y-%m', open_date)
            ORDER BY month DESC
            LIMIT 12
        """).fetchall()
        
        # 盈亏分布（区间统计）
        distribution = conn.execute("""
            SELECT 
                CASE 
                    WHEN close_profit <= -0.10 THEN '≤ -10%'
                    WHEN close_profit <= -0.05 THEN '-10% ~ -5%'
                    WHEN close_profit <= -0.02 THEN '-5% ~ -2%'
                    WHEN close_profit < 0 THEN '-2% ~ 0%'
                    WHEN close_profit = 0 THEN '0%'
                    WHEN close_profit <= 0.02 THEN '0% ~ 2%'
                    WHEN close_profit <= 0.05 THEN '2% ~ 5%'
                    WHEN close_profit <= 0.10 THEN '5% ~ 10%'
                    WHEN close_profit <= 0.20 THEN '10% ~ 20%'
                    ELSE '> 20%'
                END as profit_range,
                COUNT(*) as count
            FROM trades
            WHERE is_open = 0
            GROUP BY 1
            ORDER BY MIN(close_profit)
        """).fetchall()
        
        conn.close()
        
        return {
            "by_direction": [_row_to_dict(r) for r in by_direction],
            "by_exit_reason": [_row_to_dict(r) for r in by_exit],
            "monthly": [_row_to_dict(r) for r in monthly],
            "profit_distribution": [_row_to_dict(r) for r in distribution],
            "cached_at": datetime.now(timezone.utc).isoformat(),
        }
    except Exception as e:
        raise HTTPException(status_code=500, detail=f"Database error: {str(e)}")


# =============================================
# 启动
# =============================================
if __name__ == "__main__":
    port = int(os.environ.get("PORT", 8000))
    uvicorn.run("main:app", host="0.0.0.0", port=port, reload=False)
