#!/usr/bin/env python3
"""端到端：AI 生成 → publish → capsule → agent 解密列表 → import + 新能力冒烟"""

from __future__ import annotations

import sys
import time
from pathlib import Path

import httpx

ROOT = Path(__file__).resolve().parents[1]
sys.path.insert(0, str(ROOT / "agent_sdk"))
from client import AgentClient, decrypt_capsule  # noqa: E402

PLATFORM = "http://127.0.0.1:8180"
AI = "http://127.0.0.1:8180/ai"


def main() -> None:
    excel = ROOT / "ai-service" / "sample_inventory.xlsx"
    if not excel.exists():
        import subprocess

        subprocess.check_call([sys.executable, str(ROOT / "ai-service" / "make_sample_excel.py")])

    with httpx.Client(timeout=60.0) as http:
        # 网关无 token 应 401
        denied = http.get(f"{PLATFORM}/api/v1/audit/logs")
        assert denied.status_code == 401, denied.text
        print("gateway jwt deny ok")

        tok = http.post(
            f"{PLATFORM}/api/v1/auth/login",
            json={"username": "demo", "password": "demo123"},
        )
        tok.raise_for_status()
        auth = tok.json()
        token, agent_key = auth["access_token"], auth["agent_key"]
        assert auth.get("role") in ("owner", "editor", "viewer"), auth
        print("login ok", auth.get("username"), "role", auth.get("role"), "tenant", auth.get("tenant_id"))

        headers = {"Authorization": f"Bearer {token}"}

        # 对象存储
        up = http.post(
            f"{PLATFORM}/api/v1/storage",
            headers=headers,
            files={"file": ("note.txt", b"hello-storage", "text/plain")},
        )
        up.raise_for_status()
        upj = up.json()
        assert upj.get("key") and upj.get("url"), upj
        print("storage upload", upj["key"])

        # 多模态（无 Key 时启发式）
        png = (
            b"\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR\x00\x00\x00\x01\x00\x00\x00\x01"
            b"\x08\x02\x00\x00\x00\x90wS\xde\x00\x00\x00\x0cIDATx\x9cc\xf8\x0f\x00"
            b"\x00\x01\x01\x00\x05\x18\xd8N\x00\x00\x00\x00IEND\xaeB`\x82"
        )
        files = {
            "excel": (
                "sample_inventory.xlsx",
                excel.read_bytes(),
                "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
            ),
            "images": ("ui_list.png", png, "image/png"),
        }
        data = {"prompt": "根据库存表和截图生成可筛选库存列表", "storage_mode": "schema_per_app"}
        gen = http.post(f"{AI}/api/v1/apps/generate", data=data, files=files)
        gen.raise_for_status()
        gj = gen.json()
        draft = gj["draft"]
        warns = " ".join(gj.get("warnings") or [])
        vision_ok = (
            "看图" in warns
            or "截图" in warns
            or bool(draft.get("meta", {}).get("source", {}).get("vision_summary"))
        )
        assert vision_ok, warns
        slug = draft["meta"]["slug"]
        resource = draft["apis"]["resources"][0]["path"].lstrip("/")
        print("generate", slug, resource, "confidence", gj["confidence"])

        # 唯一 slug，避免重复发布
        slug = f"{slug}_{int(time.time())}"
        draft["meta"]["slug"] = slug
        draft["apis"]["base_path"] = f"/api/v1/apps/{slug}"
        print("slug", slug)

        pub = http.post(
            f"{PLATFORM}/api/v1/apps/{slug}/publish",
            headers=headers,
            json={"blueprint": draft},
        )
        if pub.status_code >= 400:
            print(pub.text)
            pub.raise_for_status()
        print("published", pub.json()["schema_name"], "memory_mode", pub.json()["memory_mode"])

        # 审计
        audit = http.get(f"{PLATFORM}/api/v1/audit/logs", headers=headers, params={"page": 1})
        audit.raise_for_status()
        actions = [x.get("action") for x in audit.json().get("items") or []]
        assert any(a.startswith("publish.") for a in actions) or any(
            a == "storage.upload" for a in actions
        ), actions
        print("audit actions sample", actions[:5])

        cap = http.get(f"{PLATFORM}/api/v1/apps/{slug}/agent-capsule", headers=headers)
        cap.raise_for_status()
        capsule = cap.json()["capsule"]
        assert capsule.startswith("AJZ1."), capsule
        desc = decrypt_capsule(agent_key, capsule)
        assert "resources" in desc and desc["app_slug"] == slug
        print("capsule decrypted resources:", [r["name"] for r in desc["resources"]])
        assert "/api/v1/apps/" not in capsule

        client = AgentClient(token, agent_key, capsule)
        created = client.create(
            resource,
            {
                "sku": "E2E-1",
                "product_name": "测试商品",
                "warehouse": "华东仓",
                "qty": 3,
                "unit_price": 9.9,
                "status": "在售",
            },
        )
        print("agent create id", created.get("id"))
        listed = client.list(resource, warehouse="华东仓")
        print("agent list total", listed.get("total"))

        csv_body = "sku,product_name,warehouse,qty,unit_price,status\nE2E-2,导入商品,华南仓,5,19.9,在售\n"
        try:
            from openpyxl import Workbook
            import io

            wb = Workbook()
            ws = wb.active
            ws.append(["sku", "product_name", "warehouse", "qty", "unit_price", "status"])
            ws.append(["E2E-2", "导入商品", "华南仓", 5, 19.9, "在售"])
            buf = io.BytesIO()
            wb.save(buf)
            files = {
                "file": (
                    "imp.xlsx",
                    buf.getvalue(),
                    "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
                )
            }
        except Exception:
            files = {"file": ("imp.csv", csv_body.encode("utf-8"), "text/csv")}
        imp = http.post(
            f"{PLATFORM}/api/v1/apps/{slug}/{resource}/import",
            headers=headers,
            files=files,
        )
        imp.raise_for_status()
        print("import", imp.json())

        ag = http.get(
            f"{PLATFORM}/api/v1/apps/{slug}/{resource}/aggregate",
            headers=headers,
            params={"group_by": "warehouse", "sum": "qty"},
        )
        ag.raise_for_status()
        print("aggregate", ag.json())

        # database_per_app 冒烟
        data2 = {"prompt": "独立库库存台账", "storage_mode": "database_per_app"}
        gen2 = http.post(
            f"{AI}/api/v1/apps/generate",
            data=data2,
            files={
                "excel": (
                    "sample_inventory.xlsx",
                    excel.read_bytes(),
                    "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
                )
            },
        )
        gen2.raise_for_status()
        draft2 = gen2.json()["draft"]
        assert draft2["storage"]["mode"] == "database_per_app"
        slug2 = f"dbapp_{int(time.time())}"
        draft2["meta"]["slug"] = slug2
        draft2["apis"]["base_path"] = f"/api/v1/apps/{slug2}"
        pub2 = http.post(
            f"{PLATFORM}/api/v1/apps/{slug2}/publish",
            headers=headers,
            json={"blueprint": draft2},
        )
        if pub2.status_code >= 400:
            print("database_per_app publish:", pub2.text)
            pub2.raise_for_status()
        pj2 = pub2.json()
        assert pj2.get("database_name"), pj2
        print("database_per_app", pj2["database_name"], "schema", pj2["schema_name"])

        # viewer token 不应能 publish
        issued = http.post(
            f"{PLATFORM}/api/v1/auth/token",
            json={
                "tenant_id": auth["tenant_id"],
                "user_id": auth["user_id"],
                "role": "viewer",
                "secret": "dev-only-change-me",
            },
        )
        if issued.status_code == 200:
            vtok = issued.json()["access_token"]
            forbid = http.post(
                f"{PLATFORM}/api/v1/apps/{slug}/publish",
                headers={"Authorization": f"Bearer {vtok}"},
                json={"blueprint": draft},
            )
            assert forbid.status_code == 403, forbid.text
            print("rbac viewer deny publish ok")
        else:
            print("skip rbac token issue:", issued.status_code, issued.text[:120])

    print("E2E OK")


if __name__ == "__main__":
    main()
