"""
tests/test_stepan_v43_farmos.py

Unit tests for v4.3 FarmOS minimal tool pipeline.

Scope:
  - agromatrix_tools.tool_farmos_read._farmos_ping_impl (fail-closed logic)
  - crews.agromatrix_crew.operator_commands.handle_farmos_status
  - crews.agromatrix_crew.operator_commands.parse_operator_command (farmos registerd)

Без залежностей від crewai, httpx, memory-service.
Тільки monkeypatch env + mock requests.
"""
from __future__ import annotations
import os
import sys
import types
import pytest
from unittest.mock import patch, MagicMock

# ── Шляхи ────────────────────────────────────────────────────────────────────
_ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
_PKG = os.path.join(_ROOT, "packages", "agromatrix-tools")
_CREWS = os.path.join(_ROOT, "crews")
for _p in (_PKG, _CREWS):
    if _p not in sys.path:
        sys.path.insert(0, _p)

# ── Мок недоступних модулів до будь-якого імпорту agromatrix_tools ────────────
# nats, crewai — потрібні тільки на сервері, тут мокуємо.
def _stub_module(name: str) -> types.ModuleType:
    m = types.ModuleType(name)
    sys.modules[name] = m
    return m

for _mod in (
    "nats", "nats.aio", "nats.aio.client",
    "crewai", "crewai.tools",
):
    if _mod not in sys.modules:
        _stub_module(_mod)

# crewai.tools.tool — повертає identity decorator
sys.modules["crewai.tools"].tool = lambda name: (lambda f: f)  # type: ignore[attr-defined]

# nats.aio.client.Client stub
sys.modules["nats.aio.client"].Client = MagicMock  # type: ignore[attr-defined]

# audit_tool_call stub (використовує nats)
import importlib, agromatrix_tools  # noqa: E402
# Форсуємо stub для audit всередині пакету
_audit_stub = types.ModuleType("agromatrix_tools.audit")
_audit_stub.audit_tool_call = lambda *a, **kw: None  # type: ignore[attr-defined]
sys.modules["agromatrix_tools.audit"] = _audit_stub

# Тепер безпечно імпортуємо
import importlib as _il  # noqa: F811
if "agromatrix_tools.tool_farmos_read" in sys.modules:
    _il.reload(sys.modules["agromatrix_tools.tool_farmos_read"])

from agromatrix_tools.tool_farmos_read import _farmos_ping_impl  # noqa: E402
from crews.agromatrix_crew.operator_commands import (  # noqa: E402
    handle_farmos_status,
    parse_operator_command,
    OPERATOR_COMMANDS,
)


# ── Fixtures ──────────────────────────────────────────────────────────────────

@pytest.fixture(autouse=True)
def clear_farmos_env(monkeypatch):
    """Прибираємо farmos env перед кожним тестом."""
    for k in ("FARMOS_BASE_URL", "FARMOS_TOKEN", "FARMOS_USER", "FARMOS_PASS", "FARMOS_PASSWORD"):
        monkeypatch.delenv(k, raising=False)
    yield


# ─── _farmos_ping_impl ────────────────────────────────────────────────────────

class TestFarmosPingImpl:
    def test_no_base_url(self):
        result = _farmos_ping_impl()
        assert "FARMOS_BASE_URL" in result
        assert "відсутній" in result.lower() or "не налаштований" in result.lower()

    def test_base_url_no_auth(self, monkeypatch):
        monkeypatch.setenv("FARMOS_BASE_URL", "http://farmos.example.com")
        result = _farmos_ping_impl()
        assert "токен" in result.lower() or "логін" in result.lower() or "auth" in result.lower()

    def test_base_url_with_token_ok(self, monkeypatch):
        monkeypatch.setenv("FARMOS_BASE_URL", "http://farmos.example.com")
        monkeypatch.setenv("FARMOS_TOKEN", "testtoken123")
        mock_resp = MagicMock()
        mock_resp.status_code = 200
        with patch("agromatrix_tools.tool_farmos_read.requests.get", return_value=mock_resp):
            result = _farmos_ping_impl()
        assert "доступний" in result.lower()
        assert "FarmOS доступний." == result

    def test_base_url_with_token_401(self, monkeypatch):
        monkeypatch.setenv("FARMOS_BASE_URL", "http://farmos.example.com")
        monkeypatch.setenv("FARMOS_TOKEN", "badtoken")
        mock_resp = MagicMock()
        mock_resp.status_code = 401
        with patch("agromatrix_tools.tool_farmos_read.requests.get", return_value=mock_resp):
            result = _farmos_ping_impl()
        assert "401" in result
        assert "авторизац" in result.lower()

    def test_base_url_with_token_403(self, monkeypatch):
        monkeypatch.setenv("FARMOS_BASE_URL", "http://farmos.example.com")
        monkeypatch.setenv("FARMOS_TOKEN", "limitedtoken")
        mock_resp = MagicMock()
        mock_resp.status_code = 403
        with patch("agromatrix_tools.tool_farmos_read.requests.get", return_value=mock_resp):
            result = _farmos_ping_impl()
        assert "403" in result
        assert "авторизац" in result.lower()

    def test_base_url_with_token_503(self, monkeypatch):
        monkeypatch.setenv("FARMOS_BASE_URL", "http://farmos.example.com")
        monkeypatch.setenv("FARMOS_TOKEN", "tok")
        mock_resp = MagicMock()
        mock_resp.status_code = 503
        with patch("agromatrix_tools.tool_farmos_read.requests.get", return_value=mock_resp):
            result = _farmos_ping_impl()
        assert "503" in result

    def test_timeout(self, monkeypatch):
        monkeypatch.setenv("FARMOS_BASE_URL", "http://farmos.example.com")
        monkeypatch.setenv("FARMOS_TOKEN", "tok")
        import requests as _r
        with patch("agromatrix_tools.tool_farmos_read.requests.get",
                   side_effect=_r.exceptions.Timeout()):
            result = _farmos_ping_impl()
        assert "timeout" in result.lower()

    def test_connection_error(self, monkeypatch):
        monkeypatch.setenv("FARMOS_BASE_URL", "http://farmos.example.com")
        monkeypatch.setenv("FARMOS_TOKEN", "tok")
        import requests as _r
        with patch("agromatrix_tools.tool_farmos_read.requests.get",
                   side_effect=_r.exceptions.ConnectionError("refused")):
            result = _farmos_ping_impl()
        assert "недоступний" in result.lower()

    def test_user_pass_auth_ok(self, monkeypatch):
        monkeypatch.setenv("FARMOS_BASE_URL", "http://farmos.example.com")
        monkeypatch.setenv("FARMOS_USER", "admin")
        monkeypatch.setenv("FARMOS_PASS", "secret")
        mock_resp = MagicMock()
        mock_resp.status_code = 200
        with patch("agromatrix_tools.tool_farmos_read.requests.get", return_value=mock_resp):
            result = _farmos_ping_impl()
        assert "доступний" in result.lower()

    def test_never_reveals_url(self, monkeypatch):
        """Відповідь не містить BASE_URL або токен."""
        monkeypatch.setenv("FARMOS_BASE_URL", "http://secret-farmos.internal")
        monkeypatch.setenv("FARMOS_TOKEN", "supersecrettoken")
        mock_resp = MagicMock()
        mock_resp.status_code = 200
        with patch("agromatrix_tools.tool_farmos_read.requests.get", return_value=mock_resp):
            result = _farmos_ping_impl()
        assert "secret-farmos" not in result
        assert "supersecrettoken" not in result

    def test_never_raises(self, monkeypatch):
        """При будь-якій помилці — повертає рядок, не кидає."""
        monkeypatch.setenv("FARMOS_BASE_URL", "http://farmos.example.com")
        monkeypatch.setenv("FARMOS_TOKEN", "tok")
        with patch("agromatrix_tools.tool_farmos_read.requests.get",
                   side_effect=RuntimeError("unexpected")):
            result = _farmos_ping_impl()
        assert isinstance(result, str)
        assert len(result) > 0


# ─── handle_farmos_status ─────────────────────────────────────────────────────

class TestHandleFarmosStatus:
    def test_no_args_returns_status(self):
        result = handle_farmos_status([])
        assert isinstance(result, dict)
        assert "summary" in result
        assert "FARMOS_BASE_URL" in result["summary"] or "FarmOS" in result["summary"]

    def test_status_arg_same_as_no_args(self):
        r1 = handle_farmos_status([])
        r2 = handle_farmos_status(["status"])
        assert r1["summary"] == r2["summary"]

    def test_unknown_subcommand(self):
        result = handle_farmos_status(["foo"])
        assert "підтримується" in result["summary"].lower() or "farmos" in result["summary"].lower()

    def test_returns_dict_with_required_keys(self):
        result = handle_farmos_status([])
        for key in ("status", "summary", "artifacts", "tool_calls", "next_actions"):
            assert key in result

    def test_with_ok_farmos(self, monkeypatch):
        monkeypatch.setenv("FARMOS_BASE_URL", "http://farmos.example.com")
        monkeypatch.setenv("FARMOS_TOKEN", "tok")
        mock_resp = MagicMock()
        mock_resp.status_code = 200
        with patch("agromatrix_tools.tool_farmos_read.requests.get", return_value=mock_resp):
            result = handle_farmos_status([])
        assert "доступний" in result["summary"].lower()

    def test_internal_error_fallback(self, monkeypatch):
        """handle_farmos_status не падає при внутрішній помилці importside."""
        # Патчимо саме там де lazy import шукає
        with patch("agromatrix_tools.tool_farmos_read._farmos_ping_impl",
                   side_effect=RuntimeError("crash")):
            result = handle_farmos_status([])
        assert "помилка" in result["summary"].lower() or "недоступний" in result["summary"].lower()


# ─── OPERATOR_COMMANDS registry ───────────────────────────────────────────────

class TestFarmosCommandRegistry:
    def test_farmos_in_operator_commands(self):
        assert "farmos" in OPERATOR_COMMANDS

    def test_parse_farmos_no_args(self):
        parsed = parse_operator_command("/farmos")
        assert parsed is not None
        assert parsed["cmd"] == "farmos"
        assert parsed["args"] == []

    def test_parse_farmos_status(self):
        parsed = parse_operator_command("/farmos status")
        assert parsed is not None
        assert parsed["cmd"] == "farmos"
        assert "status" in parsed["args"]

    def test_parse_farmos_foo(self):
        parsed = parse_operator_command("/farmos foo")
        assert parsed is not None
        assert parsed["cmd"] == "farmos"
        assert "foo" in parsed["args"]
