"""币种档案库:5m 聚合与视窗计算."""

from __future__ import annotations

import tempfile
from pathlib import Path

from lib.hub.hub_ohlcv_lib import aggregate_ohlcv_bars
from datetime import datetime, timezone
from zoneinfo import ZoneInfo

from lib.hub.hub_symbol_archive_lib import (
    CHART_DISPLAY_TZ,
    _compute_period_stats,
    _fill_missing_bars,
    init_db,
    list_daily_trades,
    load_symbol_trades,
    ms_to_wall_clock_str,
    parse_wall_clock_ms,
    resolve_archive_chart,
    trading_day_bounds_ms,
    upsert_bars_5m,
    upsert_trade_overlay,
    list_symbol_rows,
    upsert_trades_cache,
)


def _seed_5m_bars(
    db: Path,
    start_ms: int,
    count: int,
    step: int = 300_000,
    *,
    ex: str = "gate",
    sym: str = "ONDO",
) -> None:
    bars = []
    price = 1.0
    for i in range(count):
        o = start_ms + i * step
        price += 0.001
        bars.append(
            {
                "open_time_ms": o,
                "open": price,
                "high": price + 0.002,
                "low": price - 0.001,
                "close": price + 0.001,
                "volume": 100 + i,
            }
        )
    upsert_bars_5m(ex, sym, bars, db_path=db)


def test_aggregate_15m_from_5m():
    start = 1_700_000_000_000
    bars = []
    for i in range(6):
        t = start + i * 300_000
        bars.append(
            {
                "open_time_ms": t,
                "open": 1.0,
                "high": 1.1,
                "low": 0.9,
                "close": 1.05,
                "volume": 10,
            }
        )
    agg = aggregate_ohlcv_bars(bars, "15m")
    assert len(agg) >= 1
    assert agg[-1]["close"] == bars[-1]["close"]
    assert agg[0]["open_time_ms"] <= agg[1]["open_time_ms"]


def test_resolve_archive_chart_15m():
    with tempfile.TemporaryDirectory() as td:
        db = Path(td) / "archive.db"
        init_db(db)
        anchor = 1_700_000_000_000
        _seed_5m_bars(db, anchor - 50 * 300_000, 120)
        out = resolve_archive_chart(
            "gate",
            "ONDO",
            "15m",
            anchor_ms=anchor,
            mode="hold",
            bars=40,
            db_path=db,
        )
        assert out["ok"] is True
        assert out["timeframe"] == "15m"
        assert len(out["candles"]) >= 10


def test_fill_missing_bars_continuity():
    period = 300_000
    start = (1_700_000_000_000 // period) * period
    bars = [
        {
            "open_time_ms": start,
            "open": 1.0,
            "high": 1.1,
            "low": 0.9,
            "close": 1.05,
            "volume": 10,
        },
        {
            "open_time_ms": start + period * 2,
            "open": 1.05,
            "high": 1.15,
            "low": 1.0,
            "close": 1.1,
            "volume": 8,
        },
    ]
    filled = _fill_missing_bars(bars, period, start, start + period * 2)
    assert len(filled) >= 3
    assert any(b.get("filled") for b in filled)


def test_resolve_archive_chart_history_range():
    with tempfile.TemporaryDirectory() as td:
        db = Path(td) / "archive.db"
        init_db(db)
        open_ms = 1_700_000_000_000
        close_ms = open_ms + 6 * 3600_000
        _seed_5m_bars(db, open_ms - 20 * 300_000, 200, ex="gate", sym="BNB/USDT")
        out = resolve_archive_chart(
            "gate",
            "BNB/USDT",
            "15m",
            opened_ms=open_ms,
            closed_ms=close_ms,
            mode="hold",
            range_mode="history",
            db_path=db,
        )
        assert out["ok"] is True
        assert out.get("range_mode") == "history"
        assert out.get("window_end_ms") <= close_ms + 4 * 3600_000
        assert len(out["candles"]) >= 40


def test_sync_prunes_missing_trades():
    with tempfile.TemporaryDirectory() as td:
        db = Path(td) / "archive.db"
        init_db(db)
        upsert_trades_cache(
            "gate",
            [
                {"id": 1, "symbol": "BNB/USDT", "result": "止损", "pnl_amount": -1},
                {"id": 2, "symbol": "BNB/USDT", "result": "止盈", "pnl_amount": 1},
            ],
            db_path=db,
            prune_missing=False,
        )
        stats = upsert_trades_cache(
            "gate",
            [{"id": 1, "symbol": "BNB/USDT", "result": "止损", "pnl_amount": -1}],
            db_path=db,
            prune_missing=True,
        )
        rows = load_symbol_trades("gate", "BNB/USDT", db_path=db)
        assert len(rows) == 1
        assert rows[0]["trade_id"] == 1
        assert stats["removed"] == 1


def test_list_with_overlay_filters():
    with tempfile.TemporaryDirectory() as td:
        db = Path(td) / "archive.db"
        init_db(db)
        upsert_trades_cache(
            "gate",
            [
                {
                    "id": 1,
                    "symbol": "ONDO",
                    "direction": "long",
                    "result": "止盈",
                    "pnl_amount": 12.5,
                    "opened_at": "2026-01-01 10:00:00",
                    "closed_at": "2026-01-01 12:00:00",
                    "opened_at_ms": 1_700_000_000_000,
                    "closed_at_ms": 1_700_007_200_000,
                },
                {
                    "id": 2,
                    "symbol": "ONDO",
                    "direction": "short",
                    "result": "止损",
                    "pnl_amount": -3.2,
                    "opened_at": "2026-01-02 10:00:00",
                    "closed_at": "2026-01-02 11:00:00",
                    "opened_at_ms": 1_700_086_400_000,
                    "closed_at_ms": 1_700_090_000_000,
                },
            ],
            db_path=db,
        )
        upsert_trade_overlay("gate", 2, behavior_tag="sick", note="追高", db_path=db)
        rows = list_symbol_rows(db_path=db)
        assert len(rows) == 1
        assert rows[0]["trade_count"] == 2
        sick_only = list_symbol_rows(filter_sick=True, db_path=db)
        assert len(sick_only) == 1
        profit_only = list_symbol_rows(filter_profit=True, db_path=db)
        assert len(profit_only) == 1


def test_parse_wall_clock_ms_uses_utc_plus_8():
    ms = parse_wall_clock_ms("2026-06-07 20:30:00")
    assert ms is not None
    dt_utc = datetime.fromtimestamp(ms / 1000.0, tz=timezone.utc)
    dt_bj = dt_utc.astimezone(CHART_DISPLAY_TZ)
    assert dt_bj.strftime("%Y-%m-%d %H:%M:%S") == "2026-06-07 20:30:00"
    assert ms_to_wall_clock_str(ms) == "2026-06-07 20:30:00"
    assert parse_wall_clock_ms("2026-06-07 20:30") == ms


def test_parse_wall_clock_ms_accepts_epoch_strings():
    ms = 1_700_000_000_000
    assert parse_wall_clock_ms(str(ms)) == ms
    assert parse_wall_clock_ms(str(ms // 1000)) == ms


def test_resolve_archive_chart_history_uses_trade_span_not_200_bars():
    with tempfile.TemporaryDirectory() as td:
        db = Path(td) / "archive.db"
        init_db(db)
        opened = 1_700_000_000_000
        closed = opened + 20 * 24 * 3600_000
        _seed_5m_bars(db, opened - 35 * 24 * 3600_000, 40 * 24 * 12)
        out = resolve_archive_chart(
            "gate",
            "ONDO",
            "15m",
            opened_ms=opened,
            closed_ms=closed,
            mode="hold",
            bars=200,
            range_mode="history",
            db_path=db,
        )
        assert out["ok"] is True
        assert out["range_mode"] == "history"
        assert out["bar_count"] > 200


def test_upsert_forces_sync_exchange_key():
    with tempfile.TemporaryDirectory() as td:
        db = Path(td) / "archive.db"
        init_db(db)
        upsert_trades_cache(
            "gate",
            [
                {
                    "id": 77,
                    "exchange_key": "gate",
                    "account_exchange_key": "gate",
                    "symbol": "ETH/USDT",
                    "result": "止损",
                    "pnl_amount": -1,
                    "opened_at_ms": 1_700_000_000_000,
                    "closed_at_ms": 1_700_007_200_000,
                }
            ],
            db_path=db,
        )
        rows = load_symbol_trades("gate", "ETH/USDT", db_path=db)
        assert len(rows) == 1
        assert rows[0]["exchange_key"] == "gate"
        assert "account_exchange_key" not in rows[0]


def test_compute_period_stats_win_loss_metrics():
    rows = [
        {"exchange_key": "binance", "pnl_amount": 10.0, "behavior_tag": ""},
        {"exchange_key": "binance", "pnl_amount": 4.0, "behavior_tag": ""},
        {"exchange_key": "okx", "pnl_amount": -3.0, "behavior_tag": "sick"},
        {"exchange_key": "okx", "pnl_amount": -6.0, "behavior_tag": ""},
    ]
    st = _compute_period_stats(rows)
    assert st["open_count"] == 4
    assert st["win_count"] == 2
    assert st["loss_count"] == 2
    assert st["avg_win"] == 7.0
    assert st["avg_loss"] == -4.5
    assert st["max_win"] == 10.0
    assert st["max_loss"] == -6.0
    assert st["win_rate"] == 50.0
    assert st["profit_loss_ratio"] == round(7.0 / 4.5, 2)
    assert st["sick_count"] == 1
    assert st["pnl_total"] == 5.0
    assert st["pnl_ex_sick"] == 8.0
    assert st["by_exchange"]["binance"]["win_count"] == 2
    assert st["by_exchange"]["binance"]["win_rate"] == 100.0
    assert st["by_exchange"]["binance"]["profit_loss_ratio"] is None


def test_list_daily_trades_search_filters_stats():
    with tempfile.TemporaryDirectory() as td:
        db = Path(td) / "archive.db"
        init_db(db)
        day = "2023-11-15"
        start_ms, _ = trading_day_bounds_ms(day)
        btc_close = start_ms + 3_600_000
        eth_close = start_ms + 7_200_000
        upsert_trades_cache(
            "gate",
            [
                {
                    "id": 1,
                    "symbol": "BTC/USDT",
                    "result": "止盈",
                    "pnl_amount": 5.0,
                    "opened_at_ms": start_ms,
                    "closed_at_ms": btc_close,
                },
                {
                    "id": 2,
                    "symbol": "ETH/USDT",
                    "result": "止损",
                    "pnl_amount": -2.0,
                    "opened_at_ms": btc_close,
                    "closed_at_ms": eth_close,
                },
            ],
            db_path=db,
        )
        payload = list_daily_trades(
            period="range",
            date_from=day,
            date_to=day,
            search="btc",
            db_path=db,
        )
        assert len(payload["trades"]) == 1
        assert payload["trades"][0]["symbol"] == "BTC/USDT"
        st = payload["stats"]
        assert st["open_count"] == 1
        assert st["win_count"] == 1
        assert st["loss_count"] == 0
        assert st["max_win"] == 5.0
        assert st["pnl_total"] == 5.0
