from app import backtest as bt
from app.analyzer import build_analysis_cache, build_number_weights, score_combination


def _toy_draws(n=120):
    # 결정적 가짜 회차: 분석 캐시 구성용 (오름차순 (drw_no, [6 nums]))
    import random as _r
    _r.seed(1)
    out = []
    for i in range(1, n + 1):
        nums = sorted(_r.sample(range(1, 46), 6))
        out.append((i, nums))
    return out


def test_grade_tickets_histogram_and_prizes():
    winning6 = [1, 2, 3, 4, 5, 6]
    bonus = 7
    tickets = [
        [1, 2, 3, 4, 5, 6],     # 6일치 = 1등
        [1, 2, 3, 4, 5, 7],     # 5일치 + 보너스 = 2등
        [1, 2, 3, 4, 5, 8],     # 5일치 = 3등
        [1, 2, 3, 4, 9, 10],    # 4일치 = 4등
        [1, 2, 3, 11, 12, 13],  # 3일치 = 5등
        [40, 41, 42, 43, 44, 45],  # 0일치
    ]
    r = bt.grade_tickets(tickets, winning6, bonus)
    assert r["m6"] == 1
    assert r["m5"] == 2           # 5일치 총 2장(보너스 포함)
    assert r["bonus_hits"] == 1   # 그 중 보너스 1장
    assert r["m4"] == 1
    assert r["m3"] == 1
    assert r["best_match"] == 6
    # 등수 매핑 헬퍼
    prizes = bt.prize_counts(r)
    assert prizes == {"1st": 1, "2nd": 1, "3rd": 1, "4th": 1, "5th": 1}


def test_purchase_tickets_distinct_and_count():
    draws = _toy_draws()
    cache = build_analysis_cache(draws)
    nw = build_number_weights(cache)
    pool = bt.generate_pool(cache, nw, n=2000, seed=7)
    W = [0.25, 0.30, 0.20, 0.15, 0.10]
    bought = bt.purchase_tickets(pool, cache, W, k=50)
    assert len(bought) == 50
    assert len({tuple(t) for t in bought}) == 50           # distinct
    # W로 랭킹된 상위 k → 평균 점수가 풀 전체 평균 이상이어야
    avg_bought = sum(score_combination(t, cache, W)["score_total"] for t in bought) / 50
    avg_pool = sum(score_combination(t, cache, W)["score_total"] for t in pool) / len(pool)
    assert avg_bought >= avg_pool


def test_random_null_and_coverage_distinct():
    rnd = bt.random_null_tickets(k=50, seed=3)
    assert len(rnd) == 50 and len({tuple(t) for t in rnd}) == 50
    cov = bt.coverage_tickets(k=9, seed=3)   # 9장 = 54슬롯 ≥ 45번호 전수 커버 가능
    flat = {n for t in cov for n in t}
    assert len(cov) == 9 and len({tuple(t) for t in cov}) == 9
    assert len(flat) >= 40   # 커버리지 전략은 번호를 넓게 퍼뜨림


def test_point_in_time_excludes_target_draw():
    draws = _toy_draws(50)   # drw_no 1..50
    pit = bt.point_in_time_draws(draws, target_draw_no=30)
    assert all(d < 30 for d, _ in pit)   # 30 이상 제외
    assert max(d for d, _ in pit) == 29
    assert len(pit) == 29


def test_calibrate_winner_scores_and_percentile():
    draws = _toy_draws(60)
    winning6 = [3, 11, 19, 27, 35, 44]
    res = bt.calibrate_winner_compute(draws, target_draw_no=60,
                                      winning6=winning6, sample_m=500, seed=9)
    assert set(res["scores"].keys()) >= {"score_total", "score_frequency",
        "score_fingerprint", "score_gap", "score_cooccur", "score_diversity"}
    assert 0.0 <= res["percentile"] <= 1.0
    assert res["cache_draws"] == 59      # 1..59


def test_generate_pool_partial_fill(monkeypatch):
    """weighted_sample_6이 항상 같은 조합만 반환하도록 패치 → cap에 먼저 걸려 len < n — 예외 없이 반환."""
    import random as _r
    _r.seed(42)
    tiny_draws = [(i, sorted(_r.sample(range(1, 46), 6))) for i in range(1, 10)]
    cache = build_analysis_cache(tiny_draws)
    nw = build_number_weights(cache)

    # weighted_sample_6을 항상 동일한 하나의 조합만 반환하도록 패치
    # → 두 번째 시도부터 seen에 막혀 n개를 채울 수 없고 cap=n*4 이후 종료
    import app.backtest as _bt_mod
    monkeypatch.setattr(_bt_mod, "weighted_sample_6", lambda _w: [1, 2, 3, 4, 5, 6])

    n = 50
    pool = bt.generate_pool(cache, nw, n=n, seed=0)
    # 예외 없이 반환해야 하고, 결과는 n 미만이어야 하며 모두 distinct
    assert isinstance(pool, list)
    assert len(pool) < n
    assert len({tuple(t) for t in pool}) == len(pool)
