"""Tests for centralized online learning coordination."""

from __future__ import annotations

import httpx
import pytest

from logos_adk.agent import Agent
from logos_adk.learning import (
    FeedbackAPI,
    FeedbackRecord,
    LearningCoordinator,
    MemoryLearningEngine,
    MemoryLearningTarget,
    ModelRoutingLearningSubscriber,
    ModelRoutingLearningEngine,
    ModelRoutingSyncTarget,
    RetrievalLearningSubscriber,
    RetrievalLearningEngine,
    RetrievalSyncTarget,
    RunRecord,
    SQLiteLearningStore,
    WorkflowLearningSubscriber,
    WorkflowSyncTarget,
)
from logos_adk.memory import MemoryEntry
from logos_adk.memory.backends import InMemoryBackend
from logos_adk.providers.mock import MockProvider
from logos_adk.rag import Chunk, KnowledgeBase
from logos_adk.rag.vector_store import InMemoryVectorStore


async def _request(app, method: str, path: str, **kwargs):
    transport = httpx.ASGITransport(app=app)
    async with httpx.AsyncClient(transport=transport, base_url="http://testserver") as client:
        return await client.request(method, path, **kwargs)


def _embed(text: str) -> list[float]:
    base = float(sum(ord(char) for char in text) % 13)
    return [base, len(text) / 100.0]


def _seed_run(store: SQLiteLearningStore, *, trace_id: str, model: str = "claude-sonnet-4-20250514", metadata: dict | None = None) -> None:
    store.create_run(
        RunRecord(
            id=f"run-{trace_id}",
            trace_id=trace_id,
            request_id=f"req-{trace_id}",
            agent_name="bot",
            input_text="Answer the refund question",
            output_text="draft answer",
            session_id="acme:lead-1",
            workspace_id="acme",
            prompt_version="v1",
            model=model,
            config_fingerprint="cfg-v1",
            metadata=dict(metadata or {"task_type": "support", "user_segment": "vip"}),
        )
    )


def _seed_retrieval_event(engine: RetrievalLearningEngine, *, trace_id: str) -> None:
    engine.record_retrieval(
        trace_id=trace_id,
        session_id="acme:lead-1",
        workspace_id="acme",
        task_type="support",
        user_segment="vip",
        prompt_name="bot",
        prompt_version="v1",
        original_query="refund policy",
        effective_query="refund policy",
        limit=2,
        filter={"topic": "refund"},
        chunks=[
            Chunk(
                content="Refund policy details",
                metadata={"source_key": "docs://refund"},
                document_id="doc-1",
            )
        ],
    )


def _seed_routing_event(engine: ModelRoutingLearningEngine, *, trace_id: str) -> None:
    engine.record_route(
        model_key="MockProvider:claude-sonnet-4-20250514",
        success=True,
        latency_ms=30,
        cost_usd=0.002,
        confidence_tier="low",
        workspace_id="acme",
        task_type="support",
        trace_id=trace_id,
    )


@pytest.mark.asyncio
async def test_learning_coordinator_syncs_memory_retrieval_and_model_routing_without_http(tmp_path):
    store = SQLiteLearningStore(str(tmp_path / "learning.db"))
    retrieval = RetrievalLearningEngine(path=str(tmp_path / "retrieval.db"))
    routing = ModelRoutingLearningEngine(path=str(tmp_path / "routing.db"))

    backend = InMemoryBackend()
    memory_learning = MemoryLearningEngine()
    entry = memory_learning.initialize_entry(
        MemoryEntry(
            content="VIP customers prefer factual and concise refund answers.",
            session_id="acme:lead-1",
        )
    )
    await backend.store(entry)

    _seed_run(
        store,
        trace_id="trace-feedback",
        metadata={
            "task_type": "support",
            "user_segment": "vip",
            "memory_entry_ids": [entry.id],
        },
    )
    _seed_retrieval_event(retrieval, trace_id="trace-feedback")
    _seed_routing_event(routing, trace_id="trace-feedback")

    coordinator = LearningCoordinator(
        store,
        memory_targets=[MemoryLearningTarget(agent_name="bot", backend=backend, learning=memory_learning)],
        retrieval_targets=[RetrievalSyncTarget(engine=retrieval)],
        model_routing_targets=[ModelRoutingSyncTarget(engine=routing)],
    )
    api = FeedbackAPI(store, coordinator=coordinator)

    feedback = api.record_feedback(
        trace_id="trace-feedback",
        kind="thumbs_down",
        failure_types=["style_miss"],
    )

    synced_entries = await backend.search("", limit=10, session_id="acme:lead-1")
    synced = next(item for item in synced_entries if item.id == entry.id)
    assert synced.harmful_count == 1
    assert synced.metadata["last_feedback_reason"] == "feedback:thumbs_down:style_miss"

    retrieval_event = retrieval.list_events(trace_id="trace-feedback")[0]
    assert feedback.id in retrieval_event.applied_feedback_ids
    assert retrieval_event.quality_score < 0

    model_scores = routing.list_model_scores(workspace_id="acme", task_type="support", confidence_tier="low")
    assert model_scores[0].average_quality_score < 0


def test_learning_coordinator_supports_outcome_only_path_and_trace_idempotency(tmp_path):
    store = SQLiteLearningStore(str(tmp_path / "learning.db"))
    retrieval = RetrievalLearningEngine(path=str(tmp_path / "retrieval.db"))
    routing = ModelRoutingLearningEngine(path=str(tmp_path / "routing.db"))

    _seed_run(store, trace_id="trace-outcome")
    _seed_retrieval_event(retrieval, trace_id="trace-outcome")
    _seed_routing_event(routing, trace_id="trace-outcome")

    coordinator = LearningCoordinator(
        store,
        retrieval_targets=[RetrievalSyncTarget(engine=retrieval)],
        model_routing_targets=[ModelRoutingSyncTarget(engine=routing)],
    )
    api = FeedbackAPI(store, coordinator=coordinator)

    outcome = api.record_outcome(
        trace_id="trace-outcome",
        kind="conversion",
        value=True,
    )

    retrieval_event = retrieval.list_events(trace_id="trace-outcome")[0]
    assert outcome.id in retrieval_event.applied_outcome_ids
    assert retrieval_event.quality_score > 0

    model_scores = routing.list_model_scores(workspace_id="acme", task_type="support", confidence_tier="low")
    assert model_scores[0].average_quality_score > 0

    duplicate = coordinator.sync_trace("trace-outcome")
    assert duplicate.skipped is True
    assert duplicate.skipped_reason == "idempotent_no_new_signals"


def test_learning_coordinator_supports_explicit_subscribers_and_workflow_placeholder(tmp_path):
    store = SQLiteLearningStore(str(tmp_path / "learning.db"))
    retrieval = RetrievalLearningEngine(path=str(tmp_path / "retrieval.db"))
    routing = ModelRoutingLearningEngine(path=str(tmp_path / "routing.db"))

    _seed_run(store, trace_id="trace-subscribers")
    _seed_retrieval_event(retrieval, trace_id="trace-subscribers")
    _seed_routing_event(routing, trace_id="trace-subscribers")
    store.create_feedback(
        FeedbackRecord(
            id="fb-1",
            kind="thumbs_up",
            trace_id="trace-subscribers",
            run_id="run-trace-subscribers",
            workspace_id="acme",
            agent_name="bot",
        )
    )

    coordinator = LearningCoordinator(
        store,
        subscribers=[
            RetrievalLearningSubscriber(RetrievalSyncTarget(engine=retrieval)),
            ModelRoutingLearningSubscriber(ModelRoutingSyncTarget(engine=routing)),
            WorkflowLearningSubscriber(WorkflowSyncTarget(name="workflow")),
        ],
    )

    result = coordinator.sync_trace("trace-subscribers", force=True)

    assert result.retrieval_synced == 1
    assert result.model_routing_synced == 1
    assert result.workflow_synced == 0
    assert any(
        item.category == "workflow" and item.skipped and item.skipped_reason == "placeholder_no_engine"
        for item in result.subscriber_results
    )


@pytest.mark.asyncio
async def test_feedback_endpoint_uses_coordinator_for_online_sync(tmp_path):
    from logos_adk.serve import create_app

    store = SQLiteLearningStore(str(tmp_path / "learning.db"))
    retrieval = RetrievalLearningEngine(path=str(tmp_path / "retrieval.db"))
    routing = ModelRoutingLearningEngine(path=str(tmp_path / "routing.db"))

    _seed_run(store, trace_id="trace-http")
    _seed_retrieval_event(retrieval, trace_id="trace-http")
    _seed_routing_event(routing, trace_id="trace-http")

    kb = KnowledgeBase(store=InMemoryVectorStore(), embed_fn=_embed, retrieval_learning=retrieval)
    agent = (
        Agent("bot", model=MockProvider(responses=["ok"]))
        .knowledge(kb, mode="memory", limit=1)
        .model_routing_learning(routing)
    )
    app = create_app([agent], learning_store=store)

    response = await _request(
        app,
        "POST",
        "/feedback",
        json={
            "trace_id": "trace-http",
            "kind": "thumbs_up",
        },
    )
    assert response.status_code == 200

    retrieval_event = retrieval.list_events(trace_id="trace-http")[0]
    assert response.json()["id"] in retrieval_event.applied_feedback_ids
    assert retrieval_event.quality_score > 0

    model_scores = routing.list_model_scores(workspace_id="acme", task_type="support", confidence_tier="low")
    assert model_scores[0].average_quality_score > 0
