import pytest
from fastapi.testclient import TestClient


def _app(tmp_path, monkeypatch, admin=""):
    db = tmp_path / "t.sqlite3"
    monkeypatch.setenv("GOODNEWS_DB", str(db))
    monkeypatch.setenv("GOODNEWS_PUBLIC_BASE_URL", "http://testserver")
    monkeypatch.setenv("GOODNEWS_ADMIN_EMAILS", admin)
    import importlib
    import goodnews.api as api
    importlib.reload(api)
    from goodnews.db import connect, init_db
    connect(str(db)).close()
    c = connect(str(db)); init_db(c); c.close()
    return api.create_app(), api, db


def _count(db):
    from goodnews.db import connect
    c = connect(str(db)); n = c.execute("SELECT COUNT(*) FROM feedback").fetchone()[0]; c.close()
    return n


def test_feedback_stored_and_validated(tmp_path, monkeypatch):
    app, api, db = _app(tmp_path, monkeypatch)
    monkeypatch.setattr(api.email_send, "send_feedback", lambda *a, **k: None)
    tc = TestClient(app)
    assert tc.post("/api/feedback", json={"message": "  "}).status_code == 422  # empty
    assert tc.post("/api/feedback", json={"category": "idea", "message": "Love it", "visitor": "v"}).json() == {"ok": True}
    assert _count(db) == 1
    # honeypot tripped → accepted but NOT stored
    tc.post("/api/feedback", json={"message": "spam", "hp": "i am a bot", "visitor": "v"})
    assert _count(db) == 1


def test_admin_feedback_gated(tmp_path, monkeypatch):
    app, api, db = _app(tmp_path, monkeypatch, admin="boss@x.com")
    monkeypatch.setattr(api.email_send, "send_feedback", lambda *a, **k: None)
    TestClient(app).post("/api/feedback", json={"message": "hi", "visitor": "v"})
    assert TestClient(app).get("/api/admin/feedback").status_code == 401  # anon

    # sign in as admin
    tc = TestClient(app); sent = {}
    monkeypatch.setattr(api.email_send, "send_magic_link", lambda to, link: sent.update(link=link))
    tc.post("/api/auth/email/start", json={"email": "boss@x.com"})
    tc.post("/api/auth/email/verify", json={"token": sent["link"].split("token=")[1]})
    fb = tc.get("/api/admin/feedback").json()
    assert len(fb) == 1 and fb[0]["message"] == "hi"
    stats = tc.get("/api/admin/stats").json()
    assert {"funnel", "accounts", "retention", "emotional_mix", "paywall", "replace"} <= set(stats)
    assert stats["accounts"]["total"] >= 1


def _admin_client(tmp_path, monkeypatch):
    app, api, db = _app(tmp_path, monkeypatch, admin="boss@x.com")
    monkeypatch.setattr(api.email_send, "send_feedback", lambda *a, **k: None)
    TestClient(app).post("/api/feedback", json={"message": "hello there", "visitor": "v"})
    tc = TestClient(app); sent = {}
    monkeypatch.setattr(api.email_send, "send_magic_link", lambda to, link: sent.update(link=link))
    tc.post("/api/auth/email/start", json={"email": "boss@x.com"})
    tc.post("/api/auth/email/verify", json={"token": sent["link"].split("token=")[1]})
    return tc


def test_feedback_read_unread_and_delete(tmp_path, monkeypatch):
    tc = _admin_client(tmp_path, monkeypatch)
    fb = tc.get("/api/admin/feedback").json()
    fid = fb[0]["id"]
    assert fb[0]["read_at"] is None                       # starts unread
    assert tc.get("/api/admin/stats").json()["feedback_unread"] == 1

    tc.post(f"/api/admin/feedback/{fid}/read", json={"read": True})
    assert tc.get("/api/admin/feedback").json()[0]["read_at"] is not None
    assert tc.get("/api/admin/stats").json()["feedback_unread"] == 0

    tc.post(f"/api/admin/feedback/{fid}/read", json={"read": False})  # back to unread
    assert tc.get("/api/admin/stats").json()["feedback_unread"] == 1

    assert tc.delete(f"/api/admin/feedback/{fid}").json() == {"ok": True}
    assert tc.get("/api/admin/feedback").json() == []


def test_feedback_admin_actions_gated(tmp_path, monkeypatch):
    app, api, db = _app(tmp_path, monkeypatch, admin="boss@x.com")
    monkeypatch.setattr(api.email_send, "send_feedback", lambda *a, **k: None)
    TestClient(app).post("/api/feedback", json={"message": "hi", "visitor": "v"})
    anon = TestClient(app)
    assert anon.post("/api/admin/feedback/1/read", json={"read": True}).status_code == 401
    assert anon.delete("/api/admin/feedback/1").status_code == 401
    assert anon.post("/api/admin/feedback/1/reply", json={"html": "hi"}).status_code == 401


def test_feedback_actions_404_on_missing_id(tmp_path, monkeypatch):
    tc = _admin_client(tmp_path, monkeypatch)
    assert tc.post("/api/admin/feedback/9999/read", json={"read": True}).status_code == 404
    assert tc.delete("/api/admin/feedback/9999").status_code == 404


def test_feedback_reply_sends_stores_marks_read(tmp_path, monkeypatch):
    app, api, db = _app(tmp_path, monkeypatch, admin="boss@x.com")
    monkeypatch.setattr(api.email_send, "send_feedback", lambda *a, **k: None)
    sent = {}
    monkeypatch.setattr(api.email_send, "send_feedback_reply",
                        lambda to, msg, html, orig: sent.update(to=to, msg=msg, html=html, orig=orig))
    TestClient(app).post("/api/feedback", json={"message": "is there an app?", "email": "reader@x.com", "visitor": "v"})
    tc = TestClient(app); link = {}
    monkeypatch.setattr(api.email_send, "send_magic_link", lambda to, l: link.update(l=l))
    tc.post("/api/auth/email/start", json={"email": "boss@x.com"})
    tc.post("/api/auth/email/verify", json={"token": link["l"].split("token=")[1]})
    fid = tc.get("/api/admin/feedback").json()[0]["id"]
    r = tc.post(f"/api/admin/feedback/{fid}/reply", json={"html": "Yes — a <b>companion app</b> is coming."})
    assert r.status_code == 200
    assert sent["to"] == "reader@x.com" and "companion app" in sent["msg"] and "is there an app?" in sent["orig"]
    assert "<strong>companion app</strong>" in sent["html"]   # sanitized editor HTML (b→strong)
    fb = tc.get("/api/admin/feedback").json()[0]
    assert fb["read_at"] is not None                       # marked read on reply
    rep = fb["replies"][0]
    assert len(fb["replies"]) == 1 and rep["sent_to"] == "reader@x.com"
    assert "<strong>companion app</strong>" in rep["message_html"]  # stored HTML


def test_feedback_reply_requires_address_and_message(tmp_path, monkeypatch):
    tc = _admin_client(tmp_path, monkeypatch)  # posts anonymous feedback (no email)
    monkeypatch.setattr("goodnews.email_send.send_feedback_reply",
                        lambda *a, **k: (_ for _ in ()).throw(AssertionError("should not send")))
    fid = tc.get("/api/admin/feedback").json()[0]["id"]
    assert tc.post(f"/api/admin/feedback/{fid}/reply", json={"html": "  "}).status_code == 422
    assert tc.post(f"/api/admin/feedback/{fid}/reply", json={"html": "hi"}).status_code == 400  # no address
    assert tc.post("/api/admin/feedback/9999/reply", json={"html": "hi"}).status_code == 404
