"""Integration tests for app.api.v1.admin — superuser-only management endpoints."""

import uuid

import pytest
import pytest_asyncio
from sqlalchemy import select

from app.models.literature import GlobalTag
from app.models.user import Tenant, User, UserTenant


@pytest_asyncio.fixture
async def superuser_ctx(client, db):
    """Register a user, promote to superuser, re-login, return (headers, user_id)."""
    import uuid as _uuid
    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()
    user_id = uuid.UUID(data["user"]["id"])

    # Promote to platform admin in DB
    r = await db.execute(select(User).where(User.id == user_id))
    u = r.scalar()
    u.platform_role = "platform_owner"
    await db.commit()

    # Re-login to get JWT with platform_role=platform_owner
    resp2 = await client.post("/api/v1/auth/login", json={
        "email": email, "password": "Admin1234",
    })
    assert resp2.status_code == 200
    headers = {"Authorization": f"Bearer {resp2.json()['token']['access_token']}"}
    return headers, user_id


@pytest_asyncio.fixture
async def seed_admin_tag(db):
    """Create a single GlobalTag for tag-related tests."""
    tag = GlobalTag(
        id=uuid.uuid4(), name_zh="测试标签", name_en="test-tag",
        path="test::tag", tag_category="test", level=1, is_selectable=True,
    )
    db.add(tag)
    await db.commit()
    return tag


# ── Auth guard ──


@pytest.mark.asyncio
async def test_admin_requires_superuser(client):
    """Non-superuser gets 403 on admin endpoints."""
    email = f"user{uuid.uuid4().hex[:6]}@test.cn"
    resp = await client.post("/api/v1/auth/register", json={
        "email": email, "password": "Test1234", "display_name": "Regular",
    })
    headers = {"Authorization": f"Bearer {resp.json()['token']['access_token']}"}
    r = await client.get("/api/v1/admin/tenants", headers=headers)
    assert r.status_code == 403
    error_msg = r.json().get("error", {}).get("message", "")
    assert "platform" in error_msg.lower()


# ── Tenants ──


@pytest.mark.asyncio
async def test_list_tenants(superuser_ctx, client):
    """List tenants returns at least the superuser's own tenant."""
    headers, _ = superuser_ctx
    r = await client.get("/api/v1/admin/tenants", headers=headers)
    assert r.status_code == 200
    data = r.json()
    assert data["total"] >= 1
    assert len(data["items"]) >= 1
    assert "name" in data["items"][0]
    assert "slug" in data["items"][0]


@pytest.mark.asyncio
async def test_list_tenants_with_search(superuser_ctx, client):
    """Search tenants by name."""
    headers, _ = superuser_ctx
    r = await client.get("/api/v1/admin/tenants?q=个人空间", headers=headers)
    assert r.status_code == 200
    assert r.json()["total"] >= 1


@pytest.mark.asyncio
async def test_get_tenant(superuser_ctx, client, db):
    """Get tenant detail by ID."""
    headers, uid = superuser_ctx
    r = await db.execute(
        select(Tenant.id).join(UserTenant).where(UserTenant.user_id == uid)
    )
    tid = str(r.scalar())
    r = await client.get(f"/api/v1/admin/tenants/{tid}", headers=headers)
    assert r.status_code == 200
    data = r.json()
    assert data["id"] == tid
    assert "plan_type" in data
    assert "member_count" in data


@pytest.mark.asyncio
async def test_get_tenant_not_found(superuser_ctx, client):
    """Get non-existent tenant returns 404."""
    headers, _ = superuser_ctx
    r = await client.get(f"/api/v1/admin/tenants/{uuid.uuid4()}", headers=headers)
    assert r.status_code == 404


@pytest.mark.asyncio
async def test_update_tenant(superuser_ctx, client, db):
    """Update tenant fields."""
    headers, uid = superuser_ctx
    r = await db.execute(
        select(Tenant.id).join(UserTenant).where(UserTenant.user_id == uid)
    )
    tid = str(r.scalar())
    r = await client.put(f"/api/v1/admin/tenants/{tid}",
                         json={"hospital_name": "测试医院"}, headers=headers)
    assert r.status_code == 200
    assert r.json()["status"] == "updated"

    # Verify
    t = (await db.execute(select(Tenant).where(Tenant.id == uuid.UUID(tid)))).scalar()
    assert t.hospital_name == "测试医院"


@pytest.mark.asyncio
async def test_toggle_tenant_status(superuser_ctx, client, db):
    """Toggle tenant active/suspended."""
    headers, uid = superuser_ctx
    r = await db.execute(
        select(Tenant.id).join(UserTenant).where(UserTenant.user_id == uid)
    )
    tid = str(r.scalar())
    r = await client.post(f"/api/v1/admin/tenants/{tid}/toggle-status", headers=headers)
    assert r.status_code == 200
    assert r.json()["new_status"] == "suspended"

    # Toggle back
    r = await client.post(f"/api/v1/admin/tenants/{tid}/toggle-status", headers=headers)
    assert r.status_code == 200
    assert r.json()["new_status"] == "active"


# ── Users ──


@pytest.mark.asyncio
async def test_list_users(superuser_ctx, client):
    """List users returns at least the admin user."""
    headers, uid = superuser_ctx
    r = await client.get("/api/v1/admin/users", headers=headers)
    assert r.status_code == 200
    data = r.json()
    assert data["total"] >= 1
    assert len(data["items"]) >= 1
    assert "email" in data["items"][0]
    assert "tenants" in data["items"][0]


@pytest.mark.asyncio
async def test_list_users_search(superuser_ctx, client, db):
    """Search users by email."""
    headers, uid = superuser_ctx
    # Create another user to search for
    email = f"searchme{uuid.uuid4().hex[:6]}@test.cn"
    await client.post("/api/v1/auth/register", json={
        "email": email, "password": "Test1234", "display_name": "Searchable",
    })
    r = await client.get(f"/api/v1/admin/users?q=searchme", headers=headers)
    assert r.status_code == 200
    assert r.json()["total"] >= 1


@pytest.mark.asyncio
async def test_list_users_tenant_filter(superuser_ctx, client, db):
    """Filter users by tenant_id."""
    headers, uid = superuser_ctx
    r = await db.execute(
        select(Tenant.id).join(UserTenant).where(UserTenant.user_id == uid)
    )
    tid = str(r.scalar())
    r = await client.get(f"/api/v1/admin/users?tenant_id={tid}", headers=headers)
    assert r.status_code == 200
    assert r.json()["total"] >= 1


@pytest.mark.asyncio
async def test_list_users_tenant_filter_invalid(superuser_ctx, client):
    """Invalid tenant_id in filter just returns all users."""
    headers, uid = superuser_ctx
    r = await client.get("/api/v1/admin/users?tenant_id=not-a-uuid", headers=headers)
    assert r.status_code == 200
    # Falls back to listing all users
    assert r.json()["total"] >= 1


@pytest.mark.asyncio
async def test_update_user(superuser_ctx, client, db):
    """Update user fields."""
    headers, uid = superuser_ctx
    r = await client.put(f"/api/v1/admin/users/{uid}",
                         json={"title": "主任医师"}, headers=headers)
    assert r.status_code == 200
    assert r.json()["status"] == "updated"

    u = (await db.execute(select(User).where(User.id == uid))).scalar()
    assert u.title == "主任医师"


@pytest.mark.asyncio
async def test_update_user_not_found(superuser_ctx, client):
    """Update non-existent user returns 404."""
    headers, _ = superuser_ctx
    r = await client.put(f"/api/v1/admin/users/{uuid.uuid4()}",
                         json={"title": "Dr."}, headers=headers)
    assert r.status_code == 404


# ── Dashboard ──


@pytest.mark.asyncio
async def test_dashboard_stats(superuser_ctx, client):
    """Dashboard returns aggregate stats."""
    headers, _ = superuser_ctx
    r = await client.get("/api/v1/admin/dashboard", headers=headers)
    assert r.status_code == 200
    data = r.json()
    assert "stats" in data
    assert data["stats"]["users"] >= 1
    assert data["stats"]["tenants"] >= 1
    assert "recent_pipelines" in data


# ── Notifications ──


@pytest.mark.asyncio
async def test_list_notifications_empty(superuser_ctx, client):
    """List notifications returns empty list."""
    headers, _ = superuser_ctx
    r = await client.get("/api/v1/admin/notifications", headers=headers)
    assert r.status_code == 200
    assert r.json()["items"] == []


@pytest.mark.asyncio
async def test_create_notification(superuser_ctx, client, db):
    """Create a system notification."""
    headers, _ = superuser_ctx
    r = await client.post("/api/v1/admin/notifications", headers=headers, json={
        "title": "系统维护通知", "content": "将于凌晨2:00-4:00停机维护",
        "target_type": "all",
    })
    assert r.status_code == 200
    data = r.json()
    assert data["status"] == "created"
    assert "id" in data

    # Verify it appears in list
    r2 = await client.get("/api/v1/admin/notifications", headers=headers)
    assert r2.status_code == 200
    assert len(r2.json()["items"]) >= 1


# ── Tags ──


@pytest.mark.asyncio
async def test_admin_tags_empty(superuser_ctx, client):
    """List admin tags (possibly empty)."""
    headers, _ = superuser_ctx
    r = await client.get("/api/v1/admin/tags", headers=headers)
    assert r.status_code == 200
    assert "items" in r.json()
    assert "total" in r.json()


@pytest.mark.asyncio
async def test_admin_tags_with_data(superuser_ctx, client, seed_admin_tag):
    """List admin tags with seeded data."""
    headers, _ = superuser_ctx
    r = await client.get("/api/v1/admin/tags", headers=headers)
    assert r.status_code == 200
    data = r.json()
    assert data["total"] >= 1
    # Item should have source and is_active fields
    assert "source" in data["items"][0]
    assert "is_active" in data["items"][0]


@pytest.mark.asyncio
async def test_update_tag(superuser_ctx, client, seed_admin_tag):
    """Update a tag's name — verify via HTTP."""
    headers, _ = superuser_ctx
    tag_id = str(seed_admin_tag.id)
    r = await client.put(f"/api/v1/admin/tags/{tag_id}",
                         json={"name_zh": "更新的标签"}, headers=headers)
    assert r.status_code == 200
    assert r.json()["status"] == "updated"

    # Verify via HTTP (not db fixture, which is in a separate transaction)
    r2 = await client.get("/api/v1/admin/tags", headers=headers)
    assert r2.status_code == 200
    tags_data = r2.json()
    updated = [t for t in tags_data["items"] if t["id"] == tag_id]
    assert len(updated) == 1
    assert updated[0]["name_zh"] == "更新的标签"


@pytest.mark.asyncio
async def test_update_tag_invalid_id(superuser_ctx, client):
    """Update with invalid tag ID returns 400."""
    headers, _ = superuser_ctx
    r = await client.put("/api/v1/admin/tags/not-a-uuid",
                         json={"name_zh": "test"}, headers=headers)
    assert r.status_code == 400
    assert "Invalid tag ID" in r.json()["error"]["message"]


@pytest.mark.asyncio
async def test_update_tag_not_found(superuser_ctx, client):
    """Update non-existent tag returns 404."""
    headers, _ = superuser_ctx
    r = await client.put(f"/api/v1/admin/tags/{uuid.uuid4()}",
                         json={"name_zh": "test"}, headers=headers)
    assert r.status_code == 404


# ── Journals ──


@pytest.mark.asyncio
async def test_admin_journals(superuser_ctx, client):
    """List admin journals (may be empty)."""
    headers, _ = superuser_ctx
    r = await client.get("/api/v1/admin/journals", headers=headers)
    assert r.status_code == 200
    assert "items" in r.json()
    assert "total" in r.json()


# ── Config ──


@pytest.mark.asyncio
async def test_get_config(superuser_ctx, client):
    """Get system config."""
    headers, _ = superuser_ctx
    r = await client.get("/api/v1/admin/config", headers=headers)
    assert r.status_code == 200
    data = r.json()
    assert data["specialty"] == "oncology"
    assert "debug" in data
    assert "jwt_expire_min" in data


# ── Pipeline Runs ──


@pytest.mark.asyncio
async def test_pipeline_runs_empty(superuser_ctx, client):
    """List pipeline runs (empty initially)."""
    headers, _ = superuser_ctx
    r = await client.get("/api/v1/admin/pipeline/runs", headers=headers)
    assert r.status_code == 200
    assert r.json()["items"] == []


# ── AI Summary ──


@pytest.mark.asyncio
async def test_trigger_ai_summary(superuser_ctx, client):
    """Trigger AI summary (processes articles without summary)."""
    headers, _ = superuser_ctx
    r = await client.post("/api/v1/admin/ai/summarize", headers=headers)
    assert r.status_code == 200
    data = r.json()
    assert data["status"] == "ok"
    assert "processed" in data
    assert "total" in data


# ── User Growth ──


@pytest.mark.asyncio
async def test_user_growth_unauthenticated(client):
    """Unauthenticated request to growth endpoint returns 401."""
    r = await client.get("/api/v1/admin/users/growth")
    assert r.status_code == 401


@pytest.mark.asyncio
async def test_user_growth(superuser_ctx, client):
    """User growth returns monthly data with at least the admin user."""
    headers, _ = superuser_ctx
    r = await client.get("/api/v1/admin/users/growth", headers=headers)
    assert r.status_code == 200
    data = r.json()
    assert "growth" in data
    assert len(data["growth"]) >= 1
    entry = data["growth"][0]
    assert "year" in entry
    assert "month" in entry
    assert "count" in entry
    assert entry["count"] >= 1


# ── Tag Distribution ──


@pytest.mark.asyncio
async def test_tag_distribution(superuser_ctx, client, seed_admin_tag):
    """Tag distribution returns categories with per-category counts."""
    headers, _ = superuser_ctx
    r = await client.get("/api/v1/admin/tags/distribution", headers=headers)
    assert r.status_code == 200
    data = r.json()
    assert "categories" in data
    cats = {c["category"]: c for c in data["categories"]}
    # seed_admin_tag creates a tag with tag_category="test"
    assert "test" in cats
    assert cats["test"]["count"] >= 1
    assert "articles" in cats["test"]


# ── Journal Update ──


@pytest.mark.asyncio
async def test_update_journal(superuser_ctx, client, db):
    """Update journal tier and impact_factor via PUT."""
    from sqlalchemy import delete as sql_delete
    from app.models.literature import GlobalJournal

    # Clean up and create a test journal
    await db.execute(sql_delete(GlobalJournal).where(GlobalJournal.issn == "9999-9999"))
    await db.commit()

    journal = GlobalJournal(
        id=uuid.uuid4(), name="Test Update Journal", issn="9999-9999",
        tier="C", impact_factor=0.5,
    )
    db.add(journal)
    await db.commit()

    headers, _ = superuser_ctx
    jid = str(journal.id)
    r = await client.put(
        f"/api/v1/admin/journals/{jid}",
        json={"tier": "A", "impact_factor": 5.5},
        headers=headers,
    )
    assert r.status_code == 200
    assert r.json()["status"] == "updated"

    # Verify via DB (refresh to bypass identity map cache)
    await db.refresh(journal)
    assert journal.tier == "A"
    assert journal.impact_factor == 5.5


# ── Config Update ──


@pytest.mark.asyncio
async def test_update_config(superuser_ctx, client):
    """Update system config (jwt_expire_min)."""
    headers, _ = superuser_ctx
    r = await client.put(
        "/api/v1/admin/config",
        json={"jwt_expire_min": 120},
        headers=headers,
    )
    assert r.status_code == 200
    data = r.json()
    assert data["status"] == "updated"
    assert data["jwt_expire_min"] == 120


# ── Pipeline: Refresh Citations ──


@pytest.mark.asyncio
async def test_refresh_citations(superuser_ctx, client):
    """Refresh citations returns status ok with limit param."""
    headers, _ = superuser_ctx
    r = await client.post(
        "/api/v1/admin/pipeline/refresh-citations?limit=1",
        headers=headers,
    )
    assert r.status_code == 200
    data = r.json()
    assert data["status"] == "ok"
    assert "updated" in data
    assert "total" in data
    assert "errors" in data


# ── Pipeline: Extract PICO ──


@pytest.mark.asyncio
async def test_extract_pico(superuser_ctx, client):
    """Extract PICO returns status ok (no articles to process is fine)."""
    headers, _ = superuser_ctx
    r = await client.post(
        "/api/v1/admin/pipeline/extract-pico?limit=5",
        headers=headers,
    )
    assert r.status_code == 200
    data = r.json()
    assert data["status"] == "ok"
    assert "processed" in data
    assert "total" in data


# ── Pipeline: Backfill Study Designs ──


@pytest.mark.asyncio
async def test_backfill_study_designs(superuser_ctx, client):
    """Backfill study designs returns status ok."""
    headers, _ = superuser_ctx
    r = await client.post(
        "/api/v1/admin/pipeline/backfill-study-designs",
        headers=headers,
    )
    assert r.status_code == 200
    data = r.json()
    assert data["status"] == "ok"
    assert "updated" in data


# ── Pipeline: Run ──


@pytest.mark.asyncio
async def test_run_pipeline(superuser_ctx, client):
    """Run pipeline returns status with mode and precision info."""
    headers, _ = superuser_ctx
    r = await client.post(
        "/api/v1/admin/pipeline/run?mode=broad&max_per_query=5",
        headers=headers,
        timeout=60,
    )
    assert r.status_code in (200, 202)
    if r.status_code == 200:
        data = r.json()
        assert "status" in data
