import pytest
from fastapi.testclient import TestClient
from app import db, config, selection_judge


@pytest.fixture
def client(tmp_path, monkeypatch):
    monkeypatch.setattr(config, "DB_PATH", str(tmp_path / "insta.db"))
    monkeypatch.setattr(db, "DB_PATH", str(tmp_path / "insta.db"))
    monkeypatch.setattr(selection_judge, "judge_candidates", lambda c: {})
    db.init_db()
    from app.main import app
    return TestClient(app)


def test_ranked_returns_sorted_eligible(client, monkeypatch):
    db.add_trending_keyword({"keyword": "금리", "category": "economy", "score": 0.9})
    r = client.get("/api/insta/keywords/ranked?threshold=0.0&limit=10")
    assert r.status_code == 200
    items = r.json()["items"]
    assert len(items) >= 1
    assert "final_score" in items[0] and "eligible" in items[0]


def test_decision_approve_publishes(client):
    sid = db.add_card_slate({"keyword": "금리", "category": "economy"})
    r = client.post(f"/api/insta/slates/{sid}/decision", json={"decision": "approved"})
    assert r.status_code == 200
    assert db.get_card_slate(sid)["status"] == "published"


def test_decision_reject(client):
    sid = db.add_card_slate({"keyword": "환율", "category": "economy"})
    r = client.post(f"/api/insta/slates/{sid}/decision", json={"decision": "rejected"})
    assert r.status_code == 200
    assert db.get_card_slate(sid)["status"] == "rejected"


def test_decision_invalid_400(client):
    sid = db.add_card_slate({"keyword": "x", "category": "economy"})
    r = client.post(f"/api/insta/slates/{sid}/decision", json={"decision": "maybe"})
    assert r.status_code == 400


def test_decision_unknown_slate_404(client):
    r = client.post("/api/insta/slates/99999/decision", json={"decision": "approved"})
    assert r.status_code == 404


def test_ranked_respects_dedup_window(client):
    """dedup_window_days param이 list_recent_issued_topics window에 반영되는지 검증.

    '금리' 키워드를 방금 approved(published) 상태로 기록한 뒤:
    - dedup_window_days=30 → 방금 발행 = window 안 → eligible False
    - dedup_window_days=1  → DB datetime이 정각 경계 직전이라도 여전히 1일 안이므로 eligible False
      (확인: 반드시 eligible=False)
    추가로 두 번째 키워드(word2)는 아직 발행 이력 없으므로 window 무관하게 eligible True.
    """
    # 방금 발행된 키워드 등록 + 슬레이트 approved 처리
    db.add_trending_keyword({"keyword": "금리", "category": "economy", "score": 0.9})
    sid = db.add_card_slate({"keyword": "금리", "category": "economy"})
    db.set_slate_decision(sid, "approved")  # published_at = now

    # 발행 이력 없는 키워드 추가
    db.add_trending_keyword({"keyword": "환율", "category": "economy", "score": 0.8})

    # window=30 → '금리'는 최근 발행이라 dedup 대상 → eligible False
    r = client.get("/api/insta/keywords/ranked?threshold=0.0&limit=10&dedup_window_days=30")
    assert r.status_code == 200
    items = r.json()["items"]
    keumni = next((i for i in items if i["keyword"] == "금리"), None)
    assert keumni is not None, "'금리' 항목이 ranked 응답에 없음"
    assert keumni["eligible"] is False, "dedup_window_days=30 내 발행 → eligible은 False여야 함"

    # 발행 이력 없는 '환율'은 어떤 window에서도 eligible True
    hwanul = next((i for i in items if i["keyword"] == "환율"), None)
    assert hwanul is not None, "'환율' 항목이 ranked 응답에 없음"
    assert hwanul["eligible"] is True, "발행 이력 없는 키워드는 eligible True여야 함"


def test_ranked_caps_candidates_to_judge(client, monkeypatch):
    """후보가 많아도 judge(Claude)에는 base score 상위 N(JUDGE_CANDIDATE_CAP)개만 전달.

    운영에서 미사용 키워드가 대량 누적되면 judge 프롬프트/응답이 토큰 한도를 넘어
    파싱 실패 → claude 신호가 전부 null로 degrade되던 문제 방지.
    """
    for i in range(40):
        db.add_trending_keyword({"keyword": f"kw{i}", "category": "economy", "score": i * 0.01})
    captured = {}

    def fake_judge(cands):
        captured["n"] = len(cands)
        captured["min_score"] = min(c["score"] for c in cands)
        return {}

    monkeypatch.setattr("app.selection_judge.judge_candidates", fake_judge)
    r = client.get("/api/insta/keywords/ranked?threshold=0.0&limit=100")
    assert r.status_code == 200

    from app.main import JUDGE_CANDIDATE_CAP
    assert captured["n"] == JUDGE_CANDIDATE_CAP, "judge에는 cap 개수만 전달돼야 함 (전체 X)"
    # 상위 score만 전달됐는지 — 최저 score 후보(0.0)는 제외됐어야 함
    assert captured["min_score"] > 0.0
