"""End-to-end test for BioWorkflowAction (thinking-based).

Tests the ActionSet-based bio workflow architecture:
  1. BioWorkflowAction: query analysis → empty-queue ActionSet 계획
  2. 각 ActionSet은 빈 큐 → AgentQueue가 thinking 자동 시드
  3. thinking이 subject의 도구 지시사항을 읽고 tool action 자율 실행

Simulates without LLM by mocking _analyze_query.
"""
import asyncio
import json
import sys
import os

sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", "lib"))

from core.config import AgentConfig
from core.action_set import ActionSet
from core.agent_queue import AgentQueue
from core.context import Context
from core.events import EventType
from loader.action_loader import ActionLoader
from tools.registry import ToolRegistry


def _print_event(event):
    """이벤트를 보기 좋게 출력."""
    etype = event.type.value if hasattr(event.type, "value") else str(event.type)
    data = event.data or {}

    if etype == "tool_use":
        print(f"  🔧 TOOL_USE: {data.get('name', '?')} — {data.get('purpose', '')[:60]}")
    elif etype == "tool_result":
        is_err = data.get("is_error", False)
        name = data.get("name", "?")
        marker = "❌" if is_err else "✅"
        result_preview = str(data.get("result", ""))[:80]
        print(f"  {marker} TOOL_RESULT: {name} — {result_preview}")
    elif etype == "canvas":
        print(f"  🖼️  CANVAS: {data.get('canvas_id', '?')}")
    elif etype == "canvas_update":
        updates = data.get("updates", [])
        update_str = ", ".join(f"{u.get('node_id')}={u.get('status')}" for u in updates)
        print(f"  🔄 CANVAS_UPDATE: {data.get('canvas_id', '?')} [{update_str}]")
    elif etype == "thinking":
        print(f"  💭 THINKING ({data.get('phase', '')}): {data.get('message', '')[:60]}")
    elif etype == "plan":
        phase = data.get("phase", "")
        sets = data.get("action_sets", [])
        print(f"  📋 PLAN ({phase}): {len(sets)}개 ActionSet")
        for i, s in enumerate(sets):
            print(f"     {i+1}. {s[:80]}")
    elif etype == "action_set_start":
        print(f"  ▶️  ACTION_SET_START: [{data.get('title', '')}] {data.get('subject', '')[:50]}")
    elif etype == "action_set_complete":
        print(f"  ⏹️  ACTION_SET_COMPLETE: [{data.get('title', '')}] {data.get('status', '')}")
    elif etype == "error":
        print(f"  ❗ ERROR: {data.get('message', '?')}")
    elif etype == "done":
        print(f"  🏁 DONE: {data.get('total_action_sets', '?')} action sets, {data.get('total_actions', '?')} actions")
    elif etype in ("action_start", "action_complete"):
        print(f"  📌 {etype}: type={data.get('type', '?')} status={data.get('status', '')}")
    elif etype == "response":
        print(f"  💬 RESPONSE: {str(data.get('content', ''))[:80]}")
    else:
        print(f"  📌 {etype}: {str(data)[:80]}")


async def test_bio_workflow_planning():
    """Test 1: BioWorkflowAction이 빈 큐 ActionSet 계획을 올바르게 생성하는지 확인."""
    print("=" * 60)
    print("Test 1: BioWorkflowAction Planning (thinking-based)")
    print("=" * 60)

    data_dir = "/opt/app/data"

    # Load actions
    loader = ActionLoader(os.path.join(data_dir, "actions"))
    loader.discover()

    # Create bio_workflow action
    action = loader.create(
        "bio_workflow",
        purpose="AML 환자의 RNA-seq 데이터로 항암제 저항성 마커 발굴, 외부 데이터 병합, 보고서 생성",
    )

    # Mock _analyze_query
    async def mock_analyze_query(query, context):
        return {
            "disease": "AML",
            "omics_type": "RNA-seq",
            "analysis_purpose": "drug resistance biomarker discovery",
            "search_keywords": "AML",
            "need_external_data": True,
            "external_data_reason": "멀티오믹스 통합 분석",
            "report_title": "AML 항암제 저항성 마커 발굴 분석 보고서",
            "chart_types": ["volcano", "violin", "kaplan_meier"],
            "summary": "AML RNA-seq 데이터에서 항암제 저항군/반응군 간 DEG 분석 수행",
        }

    action._analyze_query = mock_analyze_query

    # Setup mock queue with replace_queue support
    replaced_sets = []

    class MockAgentQueue:
        id = "test-queue-001"
        _tool_registry = None
        queue = []
        context = [Context.user_query("AML 분석")]
        history = []

        def get_context(self):
            return list(self.context)

        def set_context(self, ctx):
            self.context = list(ctx)

        def append_context(self, ctx):
            self.context.append(ctx)

        def replace_queue(self, sets):
            replaced_sets.extend(sets)
            self.queue = list(sets)

        def enqueue(self, s):
            self.queue.append(s)

    mock_queue = MockAgentQueue()
    action._agent_queue = mock_queue
    action._action_set = type("MockSet", (), {"id": "test-set-001"})()
    action._action_loader = loader

    # Run
    context = [Context.user_query("AML 분석 요청")]
    events = []

    async for event in action.run(context):
        events.append(event)
        _print_event(event)

    print()

    # Verify output
    assert action.output is not None, "Output should be set"
    print(f"Output summary: {action.output.summary}")
    print(f"Phases: {action.output.result.get('phases', [])}")
    print(f"Created ActionSets: {len(replaced_sets)}")

    for i, aset in enumerate(replaced_sets):
        queue_len = len(aset.queue) if aset.queue else 0
        print(f"  {i+1}. [{aset.title}] queue_len={queue_len}, subject={aset.subject[:60]}...")

    # Verify PLAN event was emitted
    plan_events = [e for e in events if e.type == EventType.PLAN]
    assert len(plan_events) > 0, "PLAN event should be emitted"
    plan_data = plan_events[0].data
    print(f"\nPLAN action_sets count: {len(plan_data.get('action_sets', []))}")

    # Verify ActionSets have EMPTY queues (thinking will be auto-seeded by AgentQueue)
    assert len(replaced_sets) >= 5, (
        f"Expected at least 5 ActionSets (with external data), got {len(replaced_sets)}"
    )
    for i, aset in enumerate(replaced_sets):
        assert len(aset.queue) == 0, (
            f"ActionSet {i} should have empty queue (thinking auto-seeded), "
            f"but has {len(aset.queue)} actions"
        )
        assert aset.subject, f"ActionSet {i} should have a non-empty subject"
        assert aset.title, f"ActionSet {i} should have a non-empty title"

    # Verify BIO_STATE in context entries
    bio_state_entries = [
        c for c in action.output.context_entries
        if c.content.startswith("[BIO_STATE] ")
    ]
    assert len(bio_state_entries) == 1, "BIO_STATE should be in context entries"
    bio_state = json.loads(bio_state_entries[0].content[len("[BIO_STATE] "):])
    assert bio_state["disease"] == "AML"
    assert bio_state["need_external"] is True

    # Verify subjects contain tool direction keywords
    subjects = [aset.subject for aset in replaced_sets]
    assert any("platform_data" in s for s in subjects), (
        "At least one subject should mention platform_data tool"
    )
    assert any("pipeline" in s.lower() for s in subjects), (
        "At least one subject should mention pipeline"
    )
    assert any("report_canvas" in s for s in subjects), (
        "At least one subject should mention report_canvas tool"
    )

    print("\n✅ Test 1 PASSED\n")


async def test_bio_workflow_no_external_data():
    """Test 2: need_external_data=False 일 때 외부 데이터 단계가 생략되는지 확인."""
    print("=" * 60)
    print("Test 2: BioWorkflowAction without external data")
    print("=" * 60)

    data_dir = "/opt/app/data"

    loader = ActionLoader(os.path.join(data_dir, "actions"))
    loader.discover()

    action = loader.create(
        "bio_workflow",
        purpose="유방암 RNA-seq DEG 분석",
    )

    async def mock_analyze_query(query, context):
        return {
            "disease": "BRCA",
            "omics_type": "RNA-seq",
            "analysis_purpose": "DEG analysis",
            "search_keywords": "BRCA",
            "need_external_data": False,
            "external_data_reason": "",
            "report_title": "BRCA DEG 분석 보고서",
            "chart_types": ["volcano", "kaplan_meier"],
            "summary": "유방암 RNA-seq DEG 분석 수행",
        }

    action._analyze_query = mock_analyze_query

    replaced_sets = []

    class MockAgentQueue:
        id = "test-queue-002"
        _tool_registry = None
        queue = []
        context = [Context.user_query("BRCA 분석")]
        history = []

        def get_context(self):
            return list(self.context)

        def set_context(self, ctx):
            self.context = list(ctx)

        def append_context(self, ctx):
            self.context.append(ctx)

        def replace_queue(self, sets):
            replaced_sets.extend(sets)
            self.queue = list(sets)

        def enqueue(self, s):
            self.queue.append(s)

    mock_queue = MockAgentQueue()
    action._agent_queue = mock_queue
    action._action_set = type("MockSet", (), {"id": "test-set-002"})()
    action._action_loader = loader

    context = [Context.user_query("BRCA DEG 분석")]
    events = []
    async for event in action.run(context):
        events.append(event)
        _print_event(event)

    print()

    # Without external data: 5 phases (platform_data, export_data,
    # create_pipeline, execute_pipeline, report)
    assert len(replaced_sets) == 5, (
        f"Expected 5 ActionSets without external data, got {len(replaced_sets)}"
    )

    # No external_data subject
    subjects = [aset.subject for aset in replaced_sets]
    assert not any("external_data" in s for s in subjects), (
        "No ActionSet subject should mention external_data when need_external=False"
    )

    # All queues empty
    for i, aset in enumerate(replaced_sets):
        assert len(aset.queue) == 0, (
            f"ActionSet {i} should have empty queue, got {len(aset.queue)}"
        )

    print(f"Created {len(replaced_sets)} ActionSets (no external data)")
    print("\n✅ Test 2 PASSED\n")


async def main():
    await test_bio_workflow_planning()
    await test_bio_workflow_no_external_data()
    print("=" * 60)
    print("All tests completed!")
    print("=" * 60)


asyncio.run(main())
