import os

os.environ["ENCRYPTION_KEY"] = "OnrCzomBWbIjTf7Y-fnhL2adlU55bHZQjp8zX5zBC5w="
# #region AssistantApiTests [TYPE Module] [C:3] [SEMANTICS tests, assistant, api]
# @BRIEF Validate assistant API endpoint logic via direct async handler invocation.
# @RELATION DEPENDS_ON -> [AssistantApi]
# @INVARIANT  Every test clears assistant in-memory state before execution.
import asyncio
from datetime import UTC, datetime
from unittest.mock import MagicMock
import uuid

from src.api.routes import assistant as assistant_routes
from src.models.assistant import AssistantMessageRecord
from src.models.dataset_review import (
    ApprovalState,
    CandidateStatus,
    DatasetReviewSession,
    ExecutionMapping,
    ImportedFilter,
    MappingMethod,
    ReadinessState,
    RecommendedAction,
    SemanticCandidate,
    SemanticFieldEntry,
    SessionPhase,
    SessionStatus,
)
from src.schemas.auth import User


# #region _run_async [TYPE Function]
# @RELATION BINDS_TO -> [AssistantApiTests]
def _run_async(coro):
    return asyncio.run(coro)
# #endregion _run_async
# #region _FakeTask [TYPE Class] [C:1]
# @RELATION BINDS_TO -> [AssistantApiTests]
# @BRIEF Lightweight task model stub used as return value from _FakeTaskManager.create_task in assistant route tests.
# @INVARIANT  status is a bare string not a TaskStatus enum; callers must not depend on enum semantics.
class _FakeTask:
    def __init__(
        self,
        id,
        status="SUCCESS",
        plugin_id="unknown",
        params=None,
        result=None,
        user_id=None,
    ):
        self.id = id
        self.status = status
        self.plugin_id = plugin_id
        self.params = params or {}
        self.result = result or {}
        self.user_id = user_id
        self.started_at = datetime.now(UTC)
        self.finished_at = datetime.now(UTC)
# #endregion _FakeTask
# @DEBT: Divergent _FakeTaskManager definition. Canonical version should be in conftest.py. Authz variant is missing get_all_tasks().
# #region _FakeTaskManager [TYPE Class] [C:2]
# @RELATION BINDS_TO -> [AssistantApiTests]
# @BRIEF In-memory task manager stub that records created tasks for route-level assertions.
# @INVARIANT  create_task stores tasks retrievable by get_task/get_tasks without external side effects.
class _FakeTaskManager:
    def __init__(self):
        self.tasks = {}
    async def create_task(self, plugin_id, params, user_id=None):
        task_id = f"task-{uuid.uuid4().hex[:8]}"
        task = _FakeTask(
            task_id,
            status="STARTED",
            plugin_id=plugin_id,
            params=params,
            user_id=user_id,
        )
        self.tasks[task_id] = task
        return task
    def get_task(self, task_id):
        return self.tasks.get(task_id)
    def get_tasks(self, limit=20, offset=0):
        return sorted(self.tasks.values(), key=lambda t: t.id, reverse=True)[
            offset : offset + limit
        ]
    def get_all_tasks(self):
        return list(self.tasks.values())
# #endregion _FakeTaskManager
# #region _FakeConfigManager [TYPE Class] [C:2]
# @RELATION BINDS_TO -> [AssistantApiTests]
# @BRIEF Deterministic config stub providing hardcoded dev/prod environments and minimal settings shape for assistant route tests.
# @INVARIANT  get_config() returns anonymous inner classes, not real GlobalSettings; only default_environment_id and llm fields are safe to access.
class _FakeConfigManager:
    class _Env:
        def __init__(self, id, name):
            self.id = id
            self.name = name
    def get_environments(self):
        return [self._Env("dev", "Development"), self._Env("prod", "Production")]
    def get_config(self):
        class _Settings:
            default_environment_id = "dev"
            llm = {}
        class _Config:
            settings = _Settings()
            environments = []
        return _Config()
# #endregion _FakeConfigManager
# #region _admin_user [TYPE Function] [C:1]
# @RELATION BINDS_TO -> [AssistantApiTests]
# @BRIEF Build admin principal with spec=User for assistant route authorization tests.
def _admin_user():
    user = MagicMock(spec=User)
    user.id = "u-admin"
    user.username = "admin"
    role = MagicMock()
    role.name = "Admin"
    user.roles = [role]
    return user
# #endregion _admin_user
# #region _limited_user [TYPE Function] [C:1]
# @RELATION BINDS_TO -> [AssistantApiTests]
# @BRIEF Build limited user principal with empty roles for assistant route denial tests.
def _limited_user():
    user = MagicMock(spec=User)
    user.id = "u-limited"
    user.username = "limited"
    user.roles = []
    return user
# #endregion _limited_user
# #region _FakeQuery [TYPE Class] [C:2]
# @RELATION BINDS_TO -> [AssistantApiTests]
# @BRIEF Chainable SQLAlchemy-like query stub returning fixed item lists for assistant message persistence paths.
# @INVARIANT  filter() ignores all predicate arguments and returns self; no predicate-based filtering is emulated.
class _FakeQuery:
    def __init__(self, items):
        self.items = items
    def outerjoin(self, *args, **kwargs):
        return self
    def options(self, *args, **kwargs):
        return self
    def filter(self, *args, **kwargs):
        # @INVARIANT  filter() is predicate-blind; returns all records regardless of user_id scope
        return self
    def order_by(self, *args, **kwargs):
        return self
    def limit(self, n):
        self.items = self.items[:n]
        return self
    def offset(self, n):
        self.items = self.items[n:]
        return self
    def first(self):
        return self.items[0] if self.items else None
    def all(self):
        return self.items
    def count(self):
        return len(self.items)
# #endregion _FakeQuery
# #region _FakeDb [TYPE Class] [C:2]
# @RELATION BINDS_TO -> [AssistantApiTests]
# @BRIEF Explicit in-memory DB session double limited to assistant message persistence paths.
# @INVARIANT  query() always returns _FakeQuery with intentionally non-evaluated predicates; add/merge stay deterministic and never emulate unrelated SQLAlchemy behavior.
class _FakeDb:
    def __init__(self):
        self.added = []
        self.dataset_sessions = {}
    def query(self, model):
        if model == AssistantMessageRecord:
            return _FakeQuery([])
        if model == DatasetReviewSession:
            return _FakeQuery(list(self.dataset_sessions.values()))
        return _FakeQuery([])
    def add(self, obj):
        self.added.append(obj)
    def commit(self):
        pass
    def rollback(self):
        pass
    def merge(self, obj):
        return obj
    def refresh(self, obj):
        pass
# #endregion _FakeDb
# #region _clear_assistant_state [TYPE Function]
# @RELATION BINDS_TO -> [AssistantApiTests]
def _clear_assistant_state():
    assistant_routes.CONVERSATIONS.clear()
    assistant_routes.USER_ACTIVE_CONVERSATION.clear()
    assistant_routes.CONFIRMATIONS.clear()
    assistant_routes.ASSISTANT_AUDIT.clear()
# #endregion _clear_assistant_state
# #region _dataset_review_session [TYPE Function] [C:1]
# @RELATION BINDS_TO -> [AssistantApiTests]
# @BRIEF Build minimal owned dataset-review session fixture for assistant scoped routing tests.
def _dataset_review_session():
    session = DatasetReviewSession(
        session_id="sess-1",
        user_id="u-admin",
        environment_id="env-1",
        source_kind="superset_link",
        source_input="http://superset.local/dashboard/10",
        dataset_ref="public.sales",
        dataset_id=42,
        version=3,
        readiness_state=ReadinessState.MAPPING_REVIEW_NEEDED,
        recommended_action=RecommendedAction.APPROVE_MAPPING,
        status=SessionStatus.ACTIVE,
        current_phase=SessionPhase.MAPPING_REVIEW,
        created_at=datetime.now(UTC),
        updated_at=datetime.now(UTC),
        last_activity_at=datetime.now(UTC),
    )
    session.findings = []
    session.previews = []
    session.imported_filters = [
        ImportedFilter(
            filter_id="filter-1",
            session_id="sess-1",
            filter_name="email",
            display_name="Email",
            raw_value="john.doe@example.com",
            raw_value_masked=False,
            normalized_value="john.doe@example.com",
            source="manual",
            confidence_state="confirmed",
            requires_confirmation=False,
            recovery_status="recovered",
            notes=None,
            created_at=datetime.now(UTC),
            updated_at=datetime.now(UTC),
        )
    ]
    session.execution_mappings = [
        ExecutionMapping(
            mapping_id="map-1",
            session_id="sess-1",
            filter_id="filter-1",
            variable_id="var-1",
            mapping_method=MappingMethod.DIRECT_MATCH,
            raw_input_value="john.doe@example.com",
            effective_value="john.doe@example.com",
            transformation_note=None,
            warning_level=None,
            requires_explicit_approval=True,
            approval_state=ApprovalState.PENDING,
            approved_by_user_id=None,
            approved_at=None,
            created_at=datetime.now(UTC),
            updated_at=datetime.now(UTC),
        )
    ]
    session.semantic_fields = []
    session.semantic_fields = [
        SemanticFieldEntry(
            field_id="field-1",
            session_id="sess-1",
            field_name="customer_name",
            field_kind="dimension",
            verbose_name="Customer name",
            description="Current semantic label",
            display_format="text",
            provenance="unresolved",
            source_id=None,
            source_version=None,
            confidence_rank=None,
            is_locked=False,
            has_conflict=True,
            needs_review=True,
            last_changed_by="system",
        )
    ]
    session.semantic_fields[0].candidates = [
        SemanticCandidate(
            candidate_id="cand-1",
            field_id="field-1",
            source_id=None,
            candidate_rank=1,
            match_type="exact",
            confidence_score=0.99,
            proposed_verbose_name="Customer legal name",
            proposed_description="Approved semantic wording",
            proposed_display_format="text",
            status=CandidateStatus.PROPOSED,
        )
    ]
    session.template_variables = []
    session.clarification_sessions = []
    session.run_contexts = []
    return session
# #endregion _dataset_review_session
# #region _await_none [TYPE Function] [C:1]
# @RELATION BINDS_TO -> [AssistantApiTests]
# @BRIEF Async helper returning None for planner fallback tests.
async def _await_none(*args, **kwargs):
    return None
# #endregion _await_none
# #region test_unknown_command_returns_needs_clarification [TYPE Function]
# @RELATION BINDS_TO -> [AssistantApiTests]
# @BRIEF Unknown command should return clarification state and unknown intent.
def test_unknown_command_returns_needs_clarification(monkeypatch):
    _clear_assistant_state()
    req = assistant_routes.AssistantMessageRequest(message="some random gibberish")
    # We mock LLM planner to return low confidence
    monkeypatch.setattr(assistant_routes, "_plan_intent_with_llm", lambda *a, **k: None)
    resp = _run_async(
        assistant_routes.send_message(
            req,
            current_user=_admin_user(),
            task_manager=_FakeTaskManager(),
            config_manager=_FakeConfigManager(),
            db=_FakeDb(),
        )
    )
    assert resp.state == "needs_clarification"
    assert "уточните" in resp.text.lower() or "неоднозначна" in resp.text.lower()
# #endregion test_unknown_command_returns_needs_clarification
# #region test_capabilities_question_returns_successful_help [TYPE Function]
# @RELATION BINDS_TO -> [AssistantApiTests]
# @BRIEF Capability query should return deterministic help response.
def test_capabilities_question_returns_successful_help(monkeypatch):
    _clear_assistant_state()
    req = assistant_routes.AssistantMessageRequest(message="что ты умеешь?")
    resp = _run_async(
        assistant_routes.send_message(
            req,
            current_user=_admin_user(),
            task_manager=_FakeTaskManager(),
            config_manager=_FakeConfigManager(),
            db=_FakeDb(),
        )
    )
    assert resp.state == "success"
    assert "я могу сделать" in resp.text.lower()
# #endregion test_capabilities_question_returns_successful_help
# #region test_assistant_message_request_accepts_dataset_review_session_binding [TYPE Function]
# @RELATION BINDS_TO -> [AssistantApiTests]
# @BRIEF Assistant request schema should accept active dataset review session binding for scoped orchestration.
def test_assistant_message_request_accepts_dataset_review_session_binding():
    request = assistant_routes.AssistantMessageRequest(
        message="approve mappings",
        dataset_review_session_id="sess-1",
    )
    assert request.dataset_review_session_id == "sess-1"
# #endregion test_assistant_message_request_accepts_dataset_review_session_binding
# #region test_dataset_review_scoped_message_uses_masked_filter_context [TYPE Function]
# @RELATION BINDS_TO -> [AssistantApiTests]
# @BRIEF Session-scoped assistant context should mask imported-filter raw values before assistant-visible metadata is persisted.
def test_dataset_review_scoped_message_uses_masked_filter_context(monkeypatch):
    _clear_assistant_state()
    db = _FakeDb()
    db.dataset_sessions["sess-1"] = _dataset_review_session()
    req = assistant_routes.AssistantMessageRequest(
        message="show filters",
        dataset_review_session_id="sess-1",
    )
    assistant_routes._plan_intent_with_llm = _await_none
    async def _fake_dispatch_dataset_review_intent(
        intent, current_user, config_manager, db
    ):
        return str(intent["entities"]["summary"]), None, []
    monkeypatch.setattr(
        assistant_routes,
        "_dispatch_dataset_review_intent",
        _fake_dispatch_dataset_review_intent,
    )
    resp = _run_async(
        assistant_routes.send_message(
            req,
            current_user=_admin_user(),
            task_manager=_FakeTaskManager(),
            config_manager=_FakeConfigManager(),
            db=db,
        )
    )
    assert resp.state == "success"
    persisted_assistant = [
        item for item in db.added if getattr(item, "role", None) == "assistant"
    ][-1]
    imported_filters = persisted_assistant.payload["dataset_review_context"][
        "imported_filters"
    ]
    assert imported_filters[0]["raw_value"] == "***@example.com"
    assert imported_filters[0]["raw_value_masked"] is True
# #endregion test_dataset_review_scoped_message_uses_masked_filter_context
# #region test_dataset_review_scoped_command_returns_confirmation_for_mapping_approval [TYPE Function]
# @RELATION BINDS_TO -> [AssistantApiTests]
# @BRIEF Session-scoped assistant commands should route dataset-review mapping approvals into confirmation workflow with bound session metadata.
def test_dataset_review_scoped_command_returns_confirmation_for_mapping_approval():
    _clear_assistant_state()
    db = _FakeDb()
    db.dataset_sessions["sess-1"] = _dataset_review_session()
    req = assistant_routes.AssistantMessageRequest(
        message="approve mappings",
        dataset_review_session_id="sess-1",
    )
    assistant_routes._plan_intent_with_llm = _await_none
    resp = _run_async(
        assistant_routes.send_message(
            req,
            current_user=_admin_user(),
            task_manager=_FakeTaskManager(),
            config_manager=_FakeConfigManager(),
            db=db,
        )
    )
    assert resp.state == "needs_confirmation"
    assert resp.intent["operation"] == "dataset_review_approve_mappings"
    assert resp.intent["entities"]["dataset_review_session_id"] == "sess-1"
    assert resp.intent["entities"]["session_version"] == 3
    assert resp.intent["entities"]["mapping_ids"] == ["map-1"]
# #endregion test_dataset_review_scoped_command_returns_confirmation_for_mapping_approval
# #region test_dataset_review_scoped_command_routes_field_semantics_update [TYPE Function]
# @RELATION BINDS_TO -> [AssistantApiTests]
# @BRIEF Session-scoped assistant commands should route semantic field updates through explicit confirmation metadata.
def test_dataset_review_scoped_command_routes_field_semantics_update():
    _clear_assistant_state()
    db = _FakeDb()
    db.dataset_sessions["sess-1"] = _dataset_review_session()
    req = assistant_routes.AssistantMessageRequest(
        message='set field semantics target=field:field-1 desc="Approved semantic wording" lock',
        dataset_review_session_id="sess-1",
    )
    assistant_routes._plan_intent_with_llm = _await_none
    resp = _run_async(
        assistant_routes.send_message(
            req,
            current_user=_admin_user(),
            task_manager=_FakeTaskManager(),
            config_manager=_FakeConfigManager(),
            db=db,
        )
    )
    assert resp.state == "needs_confirmation"
    assert resp.intent["operation"] == "dataset_review_set_field_semantics"
    assert resp.intent["entities"]["dataset_review_session_id"] == "sess-1"
    assert resp.intent["entities"]["field_id"] == "field-1"
    assert resp.intent["entities"]["description"] == "Approved semantic wording"
    assert resp.intent["entities"]["lock_field"] is True
# #endregion test_dataset_review_scoped_command_routes_field_semantics_update
# #endregion AssistantApiTests
