"""User settings: rating, personal tags, feedback, academic profile"""

import uuid
import datetime

import pytest
import pytest_asyncio
from sqlalchemy import select, delete as sql_delete

from app.models.literature import GlobalLiterature
from app.models.interaction import UserLiterature, UserFeedback


PMID = 90000980
PMID2 = 90000981


@pytest_asyncio.fixture
async def seed_lit(db):
    """Seed a literature item for settings tests."""
    await db.execute(sql_delete(UserLiterature).where(
        UserLiterature.literature_id == select(GlobalLiterature.id).where(GlobalLiterature.pmid == PMID).scalar_subquery()
    ))
    await db.execute(sql_delete(GlobalLiterature).where(GlobalLiterature.pmid.in_([PMID, PMID2])))

    lit = GlobalLiterature(
        id=uuid.uuid4(), pmid=PMID, title="Settings Test Article",
        abstract="For testing user settings endpoints",
        authors=[{"family": "Li"}], journal="Test Journal",
        doi="10.1000/settings.001",
        pub_date=datetime.date(2024, 8, 1), pub_year=2024,
    )
    lit2 = GlobalLiterature(
        id=uuid.uuid4(), pmid=PMID2, title="Settings Test Article Two",
        abstract="Second article for settings tests",
        authors=[{"family": "Wang"}], journal="Another Journal",
        doi="10.1000/settings.002",
        pub_date=datetime.date(2024, 8, 15), pub_year=2024,
    )
    db.add_all([lit, lit2])
    await db.commit()
    return lit


@pytest_asyncio.fixture
async def auth_ctx(client):
    """Register a user and return (headers, user_id)."""
    email = f"t{uuid.uuid4().hex[:6]}@test.cn"
    resp = await client.post("/api/v1/auth/register", json={
        "email": email, "password": "Test1234", "display_name": "TestUser",
    })
    assert resp.status_code == 200
    data = resp.json()
    headers = {"Authorization": f"Bearer {data['token']['access_token']}"}
    user_id = uuid.UUID(data["user"]["id"])
    return headers, user_id


# ── Unauthorized Access ──


@pytest.mark.asyncio
async def test_rate_no_auth(client):
    """POST rate without auth returns 403 (CSRF blocks)."""
    r = await client.post("/api/v1/settings/literature/1/rate",
                          json={"rating": 4})
    assert r.status_code == 403


@pytest.mark.asyncio
async def test_tag_no_auth(client):
    """POST tag without auth returns 403."""
    r = await client.post("/api/v1/settings/literature/1/tag",
                          json={"tag": "test"})
    assert r.status_code == 403


@pytest.mark.asyncio
async def test_remove_tag_no_auth(client):
    """DELETE tag without auth returns 403."""
    r = await client.delete("/api/v1/settings/literature/1/tag?tag_name=test")
    assert r.status_code == 403


@pytest.mark.asyncio
async def test_feedback_no_auth(client):
    """POST feedback without auth returns 403."""
    r = await client.post("/api/v1/settings/feedback",
                          json={"detail": "No auth feedback"})
    assert r.status_code == 403


@pytest.mark.asyncio
async def test_profile_no_auth(client):
    """GET profile without auth returns 401."""
    r = await client.get("/api/v1/settings/profile")
    assert r.status_code == 401


# ── Rate Literature ──


@pytest.mark.asyncio
async def test_rate_nonexistent_pmid(client, auth_headers):
    """Rate on nonexistent pmid returns 404."""
    r = await client.post("/api/v1/settings/literature/99999999/rate",
                          json={"rating": 4}, headers=auth_headers)
    assert r.status_code == 404


@pytest.mark.asyncio
async def test_rate_not_saved(client, auth_ctx, seed_lit):
    """Rate on unsaved literature returns 400."""
    headers, _ = auth_ctx
    r = await client.post(f"/api/v1/settings/literature/{PMID}/rate",
                          json={"rating": 4}, headers=headers)
    assert r.status_code == 400


@pytest.mark.asyncio
async def test_rate_success(client, auth_ctx, seed_lit, db):
    """Rate a saved literature item."""
    headers, uid = auth_ctx
    ul = UserLiterature(id=uuid.uuid4(), user_id=uid, literature_id=seed_lit.id)
    db.add(ul)
    await db.commit()

    r = await client.post(f"/api/v1/settings/literature/{PMID}/rate",
                          json={"rating": 4}, headers=headers)
    assert r.status_code == 200
    data = r.json()
    assert data["status"] == "rated"
    assert data["rating"] == 4


@pytest.mark.asyncio
async def test_rate_clamping(client, auth_ctx, seed_lit, db):
    """Rating is clamped to 1-5 range."""
    headers, uid = auth_ctx
    ul = UserLiterature(id=uuid.uuid4(), user_id=uid, literature_id=seed_lit.id)
    db.add(ul)
    await db.commit()

    r = await client.post(f"/api/v1/settings/literature/{PMID}/rate",
                          json={"rating": 10}, headers=headers)
    assert r.status_code == 422  # ge=1, le=5 约束拒绝越界值

    r = await client.post(f"/api/v1/settings/literature/{PMID}/rate",
                          json={"rating": 0}, headers=headers)
    assert r.status_code == 422


# ── Personal Tags ──


@pytest.mark.asyncio
async def test_add_tag_nonexistent_pmid(client, auth_headers):
    """Add tag on nonexistent pmid returns 404."""
    r = await client.post("/api/v1/settings/literature/99999999/tag",
                          json={"tag": "test"}, headers=auth_headers)
    assert r.status_code == 404


@pytest.mark.asyncio
async def test_add_tag_not_saved(client, auth_ctx, seed_lit):
    """Add tag on unsaved literature returns 400."""
    headers, _ = auth_ctx
    r = await client.post(f"/api/v1/settings/literature/{PMID}/tag",
                          json={"tag": "important"}, headers=headers)
    assert r.status_code == 400


@pytest.mark.asyncio
async def test_add_tag_success(client, auth_ctx, seed_lit, db):
    """Add a personal tag to a saved literature item."""
    headers, uid = auth_ctx
    ul = UserLiterature(id=uuid.uuid4(), user_id=uid, literature_id=seed_lit.id)
    db.add(ul)
    await db.commit()

    r = await client.post(f"/api/v1/settings/literature/{PMID}/tag",
                          json={"tag": "landmark"}, headers=headers)
    assert r.status_code == 200
    data = r.json()
    assert data["status"] == "tagged"
    assert len(data["personal_tags"]) == 1
    assert data["personal_tags"][0]["name"] == "landmark"


@pytest.mark.asyncio
async def test_add_tag_multiple(client, auth_ctx, seed_lit, db):
    """Add multiple tags to a literature item."""
    headers, uid = auth_ctx
    ul = UserLiterature(id=uuid.uuid4(), user_id=uid, literature_id=seed_lit.id)
    db.add(ul)
    await db.commit()

    r = await client.post(f"/api/v1/settings/literature/{PMID}/tag",
                          json={"tag": "first"}, headers=headers)
    assert r.status_code == 200

    r = await client.post(f"/api/v1/settings/literature/{PMID}/tag",
                          json={"tag": "second"}, headers=headers)
    assert r.status_code == 200
    names = [t["name"] for t in r.json()["personal_tags"]]
    assert "first" in names
    assert "second" in names


# ── Remove Personal Tags ──


@pytest.mark.asyncio
async def test_remove_tag_nonexistent_pmid(client, auth_headers):
    """Remove tag on nonexistent pmid returns 404."""
    r = await client.delete("/api/v1/settings/literature/99999999/tag?tag_name=test",
                            headers=auth_headers)
    assert r.status_code == 404


@pytest.mark.asyncio
async def test_remove_tag_not_saved(client, auth_ctx, seed_lit):
    """Remove tag on unsaved literature returns 400."""
    headers, _ = auth_ctx
    r = await client.delete(f"/api/v1/settings/literature/{PMID}/tag?tag_name=test",
                            headers=headers)
    assert r.status_code == 400


@pytest.mark.asyncio
async def test_remove_tag_success(client, auth_ctx, seed_lit, db):
    """Remove a personal tag from a literature item."""
    headers, uid = auth_ctx
    ul = UserLiterature(id=uuid.uuid4(), user_id=uid, literature_id=seed_lit.id,
                        personal_tags=[{"name": "keep"}, {"name": "remove"}])
    db.add(ul)
    await db.commit()

    r = await client.delete(f"/api/v1/settings/literature/{PMID}/tag?tag_name=remove",
                            headers=headers)
    assert r.status_code == 200
    assert r.json()["status"] == "removed"


@pytest.mark.asyncio
async def test_remove_tag_nonexistent(client, auth_ctx, seed_lit, db):
    """Remove a tag that doesn't exist on the item."""
    headers, uid = auth_ctx
    ul = UserLiterature(id=uuid.uuid4(), user_id=uid, literature_id=seed_lit.id,
                        personal_tags=[{"name": "only"}])
    db.add(ul)
    await db.commit()

    r = await client.delete(f"/api/v1/settings/literature/{PMID}/tag?tag_name=nonexistent",
                            headers=headers)
    assert r.status_code == 200
    assert r.json()["status"] == "removed"


# ── Feedback ──


@pytest.mark.asyncio
async def test_feedback_with_auth(client, auth_headers):
    """Submit feedback returns 200 with status received."""
    r = await client.post("/api/v1/settings/feedback",
                          json={"detail": "Great app!"}, headers=auth_headers)
    assert r.status_code == 200
    data = r.json()
    assert data["status"] == "received"
    assert "id" in data


@pytest.mark.asyncio
async def test_feedback_list(client, db):
    """GET feedback list requires superuser and returns valid structure."""
    import uuid as _uuid
    from app.models.user import User
    from sqlalchemy import select
    # Register a user, promote to superuser, login
    email = f"admin{_uuid.uuid4().hex[:6]}@test.cn"
    resp = await client.post("/api/v1/auth/register", json={
        "email": email, "password": "Admin1234", "display_name": "Admin",
    })
    assert resp.status_code == 200
    data = resp.json()
    uid = _uuid.UUID(data["user"]["id"])
    r = await db.execute(select(User).where(User.id == uid))
    u = r.scalar()
    u.platform_role = "platform_owner"
    await db.commit()
    resp2 = await client.post("/api/v1/auth/login", json={
        "email": email, "password": "Admin1234",
    })
    assert resp2.status_code == 200
    admin_headers = {"Authorization": f"Bearer {resp2.json()['token']['access_token']}"}
    r = await client.get("/api/v1/settings/feedback", headers=admin_headers)
    assert r.status_code == 200
    data = r.json()
    assert "items" in data


# ── Academic Profile ──


@pytest.mark.asyncio
async def test_profile_with_auth(client, auth_headers):
    """GET profile returns stats dict."""
    r = await client.get("/api/v1/settings/profile", headers=auth_headers)
    assert r.status_code == 200
    data = r.json()
    assert "stats" in data
    stats = data["stats"]
    assert "saved" in stats
    assert "rated" in stats
    assert "notes" in stats
    assert "subscriptions" in stats
    assert "total_feed" in stats
    assert "read" in stats
    assert "active_days_7d" in stats
    assert "read_rate" in stats


@pytest.mark.asyncio
async def test_profile_with_data(client, auth_ctx, seed_lit, db):
    """Profile returns accurate counts when user has saved items."""
    headers, uid = auth_ctx

    # Save literature item
    ul = UserLiterature(id=uuid.uuid4(), user_id=uid, literature_id=seed_lit.id)
    db.add(ul)
    await db.commit()

    r = await client.get("/api/v1/settings/profile", headers=headers)
    assert r.status_code == 200
    data = r.json()
    assert data["stats"]["saved"] >= 1
    assert data["stats"]["rated"] == 0
    assert data["stats"]["notes"] == 0


@pytest.mark.asyncio
async def test_profile_top_journals(client, auth_ctx, seed_lit, db):
    """Profile returns top journals based on saved articles."""
    headers, uid = auth_ctx
    ul = UserLiterature(id=uuid.uuid4(), user_id=uid, literature_id=seed_lit.id)
    db.add(ul)
    await db.commit()

    r = await client.get("/api/v1/settings/profile", headers=headers)
    assert r.status_code == 200
    data = r.json()
    assert len(data["top_journals"]) >= 1


# ── Feedback Resolve / Delete (Superuser) ──


@pytest_asyncio.fixture
async def superuser_ctx(client, db):
    """Register a user, promote to superuser, re-login. Returns (headers, user_id)."""
    import uuid as _uuid
    from app.models.user import User
    from sqlalchemy import select

    email = f"su{_uuid.uuid4().hex[:6]}@test.cn"
    resp = await client.post("/api/v1/auth/register", json={
        "email": email, "password": "Su123456", "display_name": "SuperUser",
    })
    assert resp.status_code == 200
    data = resp.json()
    uid = _uuid.UUID(data["user"]["id"])

    r = await db.execute(select(User).where(User.id == uid))
    u = r.scalar()
    u.platform_role = "platform_owner"
    await db.commit()

    resp2 = await client.post("/api/v1/auth/login", json={
        "email": email, "password": "Su123456",
    })
    assert resp2.status_code == 200
    headers = {"Authorization": f"Bearer {resp2.json()['token']['access_token']}"}
    return headers, uid


@pytest_asyncio.fixture
async def seeded_feedback(db, auth_ctx):
    """Create a UserFeedback for the given auth_ctx user. Returns (feedback_id, headers, user_id)."""
    import uuid as _uuid

    headers, uid = auth_ctx
    fb = UserFeedback(
        id=_uuid.uuid4(),
        user_id=uid,
        feedback_type="suggestion",
        detail="Test feedback for resolve/delete tests",
    )
    db.add(fb)
    await db.commit()
    return str(fb.id), headers, uid


@pytest.mark.asyncio
async def test_feedback_resolve_superuser(client, seeded_feedback, superuser_ctx):
    """Resolve feedback as superuser returns 200."""
    fid, _, _ = seeded_feedback
    su_headers, _ = superuser_ctx
    r = await client.post(f"/api/v1/settings/feedback/{fid}/resolve", headers=su_headers)
    assert r.status_code == 200
    data = r.json()
    assert data["status"] == "resolved"


@pytest.mark.asyncio
async def test_feedback_delete_superuser(client, seeded_feedback, superuser_ctx):
    """Delete feedback as superuser returns 200."""
    fid, _, _ = seeded_feedback
    su_headers, _ = superuser_ctx
    r = await client.delete(f"/api/v1/settings/feedback/{fid}", headers=su_headers)
    assert r.status_code == 200
    data = r.json()
    assert data["status"] == "deleted"


@pytest.mark.asyncio
async def test_feedback_resolve_forbidden(client, db, seeded_feedback):
    """Non-superuser cannot resolve feedback (403)."""
    fid, headers, _ = seeded_feedback
    r = await client.post(f"/api/v1/settings/feedback/{fid}/resolve", headers=headers)
    assert r.status_code == 403


# ── Weekly Report ──


@pytest.mark.asyncio
async def test_weekly_report(client, auth_headers):
    """GET weekly report returns 200 with markdown content."""
    r = await client.get("/api/v1/settings/profile/weekly-report", headers=auth_headers)
    assert r.status_code == 200
    body = r.text
    # Should contain markdown structure
    assert "##" in body
