from io import BytesIO
from types import SimpleNamespace

import pytest
from sqlalchemy import select
from sqlalchemy.ext.asyncio import async_sessionmaker, create_async_engine
from starlette.datastructures import UploadFile

import app.models  # noqa: F401
from app.database import Base
from app.models.brain import BrainEvent, BrainMemory
from app.models.conversation import Conversation
from app.models.memory import MemorySummary, UserMemory
from app.models.user import User
from app.services import agent_service, memory_service
from app.services.agent_service import AgentService
from app.services.auth_service import get_password_hash
from app.services.document_service import DocumentService


class FakeGraph:
    async def ainvoke(self, state):
        return {"final_response": "已记录你的请求。"}


class FakeStreamingGraph:
    async def astream_events(self, state, version="v2"):
        yield {
            "event": "on_chat_model_stream",
            "name": "master",
            "data": {"chunk": SimpleNamespace(content="这是流式回复。")},
        }


@pytest.fixture
async def brain_ingestion_env(tmp_path, monkeypatch):
    db_path = tmp_path / 'test_brain_ingestion.db'
    engine = create_async_engine(f"sqlite+aiosqlite:///{db_path}", future=True)
    session_factory = async_sessionmaker(engine, expire_on_commit=False)

    async with engine.begin() as conn:
        await conn.run_sync(Base.metadata.create_all)

    async with session_factory() as session:
        user = User(
            email='brain-ingestion@example.com',
            hashed_password=get_password_hash('secret123'),
            full_name='Brain Ingestion Tester',
        )
        session.add(user)
        await session.commit()
        await session.refresh(user)

    monkeypatch.setattr(agent_service, 'get_agent_graph', lambda: FakeGraph())
    monkeypatch.setattr(agent_service, 'set_current_user', lambda user_id: None)
    monkeypatch.setattr(agent_service, 'clear_current_user', lambda: None)
    monkeypatch.setattr('app.services.document_service.settings.UPLOAD_DIR', str(tmp_path / 'uploads'))

    async with session_factory() as session:
        yield session, user

    await engine.dispose()


@pytest.mark.asyncio
async def test_chat_simple_creates_brain_event_for_user_message(brain_ingestion_env):
    session, user = brain_ingestion_env
    service = AgentService(session)

    conversation_id, _message_id, _response, _model_name = await service.chat_simple(
        user.id,
        '请记住我这周要完成知识大脑第一阶段。',
    )

    result = await session.execute(
        select(BrainEvent)
        .where(BrainEvent.user_id == user.id, BrainEvent.source_type == 'conversation')
        .order_by(BrainEvent.created_at.asc())
    )
    events = list(result.scalars().all())
    user_events = [event for event in events if event.metadata_ == {'role': 'user'}]

    assert len(user_events) == 1
    assert user_events[0].source_id == conversation_id
    assert user_events[0].event_type == 'message_created'
    assert user_events[0].title == 'User message'
    assert '知识大脑第一阶段' in (user_events[0].content_summary or '')
    assert user_events[0].status == 'pending'


@pytest.mark.asyncio
async def test_upload_document_creates_brain_event_for_document_flow(brain_ingestion_env):
    session, user = brain_ingestion_env
    service = DocumentService(session)
    upload = UploadFile(
        filename='brain-notes.md',
        file=BytesIO('# Brain\n\nCapture important product knowledge.'.encode('utf-8')),
    )

    document = await service.upload_document(user.id, upload)

    result = await session.execute(
        select(BrainEvent)
        .where(
            BrainEvent.user_id == user.id,
            BrainEvent.source_type == 'document',
            BrainEvent.source_id == document.id,
        )
    )
    event = result.scalar_one_or_none()

    assert event is not None
    assert event.event_type == 'document_uploaded'
    assert event.title == 'brain-notes.md'
    assert 'Capture important product knowledge.' in (event.content_summary or '')
    assert event.metadata_ == {
        'document_id': document.id,
        'file_type': 'md',
        'ingestion_status': 'uploaded',
    }
    assert event.status == 'pending'


@pytest.mark.asyncio
async def test_chat_simple_creates_brain_event_for_assistant_message(brain_ingestion_env):
    session, user = brain_ingestion_env
    service = AgentService(session)

    conversation_id, _message_id, response, _model_name = await service.chat_simple(
        user.id,
        '帮我总结今天知识大脑的进展。',
    )

    result = await session.execute(
        select(BrainEvent)
        .where(BrainEvent.user_id == user.id, BrainEvent.source_type == 'conversation')
        .order_by(BrainEvent.created_at.asc())
    )
    events = list(result.scalars().all())

    assert len(events) == 2
    assert events[1].source_id == conversation_id
    assert events[1].event_type == 'message_created'
    assert events[1].title == 'Assistant message'
    assert events[1].content_summary == response
    assert events[1].metadata_ == {'role': 'assistant'}


@pytest.mark.asyncio
async def test_streaming_chat_creates_brain_event_for_assistant_message(brain_ingestion_env, monkeypatch):
    session, user = brain_ingestion_env
    monkeypatch.setattr(agent_service, 'get_agent_graph', lambda: FakeStreamingGraph())
    service = AgentService(session)

    conversation_id, _message_id, stream = await service.chat(
        user.id,
        '用流式回复告诉我今天知识大脑学到了什么。',
    )

    chunks = []
    async for event in stream:
        if event.get('type') == 'chunk':
            chunks.append(event['content'])

    result = await session.execute(
        select(BrainEvent)
        .where(BrainEvent.user_id == user.id, BrainEvent.source_type == 'conversation')
        .order_by(BrainEvent.created_at.asc())
    )
    events = list(result.scalars().all())

    assert ''.join(chunks) == '这是流式回复。'
    assert len(events) == 2
    assert events[1].source_id == conversation_id
    assert events[1].event_type == 'message_created'
    assert events[1].title == 'Assistant message'
    assert events[1].content_summary == '这是流式回复。'
    assert events[1].metadata_ == {'role': 'assistant'}


@pytest.mark.asyncio
async def test_build_memory_context_includes_brain_memory_section(brain_ingestion_env):
    session, user = brain_ingestion_env
    conversation = Conversation(user_id=user.id, title='Brain context test')
    session.add(conversation)
    await session.flush()

    session.add(UserMemory(
        user_id=user.id,
        memory_type='preference',
        content='用户偏好结构化输出。',
        importance=6,
        source_conversation_id=conversation.id,
    ))
    session.add(MemorySummary(
        user_id=user.id,
        conversation_id=conversation.id,
        summary_text='之前讨论了知识大脑的整体设计。',
        turn_count=8,
    ))
    session.add(BrainMemory(
        user_id=user.id,
        memory_type='project_fact',
        title='Knowledge brain phase 1',
        content='Jarvis should learn from conversation and document events first.',
        importance=9,
        confidence=0.93,
        status='active',
        origin_source_types=['conversation', 'document'],
        metadata_={'source_count': 2},
    ))
    session.add(BrainMemory(
        user_id=user.id,
        memory_type='project_fact',
        title='Forum moderation policy',
        content='Forum moderation escalation stays separate from the current task.',
        importance=10,
        confidence=0.95,
        status='active',
        origin_source_types=['forum'],
        metadata_={'source_count': 1},
    ))
    await session.commit()

    context = await memory_service.build_memory_context(
        session,
        user.id,
        conversation.id,
        'Jarvis 接下来应该优先做什么？',
    )

    assert '【用户记忆】' in context
    assert '【之前对话摘要】' in context
    assert '【知识大脑】' in context
    assert 'Knowledge brain phase 1' in context
    assert 'Jarvis should learn from conversation and document events first.' in context
    assert 'Forum moderation policy' not in context
