"""Tests for locale support in Odoo MCP Server."""

import xmlrpc.client
from unittest.mock import MagicMock, patch

import pytest

from mcp_server_odoo.config import OdooConfig, load_config
from mcp_server_odoo.odoo_connection import OdooConnection, OdooConnectionError


@pytest.fixture
def config_with_locale():
    return OdooConfig(
        url="https://test.odoo.com",
        api_key="test_key",
        username="test",
        database="test_db",
        locale="es_ES",
        yolo_mode="true",
    )


@pytest.fixture
def config_without_locale():
    return OdooConfig(
        url="https://test.odoo.com",
        api_key="test_key",
        username="test",
        database="test_db",
        yolo_mode="true",
    )


def _make_connected(conn):
    """Set up a connection as authenticated with a mocked proxy."""
    conn._connected = True
    conn._authenticated = True
    conn._uid = 1
    conn._database = "test_db"
    conn._auth_method = "api_key"
    mock_proxy = MagicMock()
    mock_proxy.execute_kw.return_value = []
    conn._object_proxy = mock_proxy
    return mock_proxy


class TestLocaleInjection:
    def test_locale_injected_into_context(self, config_with_locale):
        conn = OdooConnection(config_with_locale)
        mock_proxy = _make_connected(conn)

        conn.execute_kw("res.partner", "search_read", [[]], {})

        passed_kwargs = mock_proxy.execute_kw.call_args[0][6]
        assert passed_kwargs["context"]["lang"] == "es_ES"

    def test_no_locale_when_not_configured(self, config_without_locale):
        conn = OdooConnection(config_without_locale)
        mock_proxy = _make_connected(conn)

        conn.execute_kw("res.partner", "search", [[]], {})

        passed_kwargs = mock_proxy.execute_kw.call_args[0][6]
        assert "context" not in passed_kwargs or "lang" not in passed_kwargs.get("context", {})

    def test_locale_preserves_existing_context(self, config_with_locale):
        conn = OdooConnection(config_with_locale)
        mock_proxy = _make_connected(conn)

        conn.execute_kw(
            "res.partner",
            "search_read",
            [[]],
            {"context": {"active_test": False, "tz": "Europe/Berlin"}},
        )

        passed_kwargs = mock_proxy.execute_kw.call_args[0][6]
        assert passed_kwargs["context"]["active_test"] is False
        assert passed_kwargs["context"]["tz"] == "Europe/Berlin"
        assert passed_kwargs["context"]["lang"] == "es_ES"

    def test_caller_lang_takes_precedence(self, config_with_locale):
        """Explicit lang in caller context should not be overwritten by ODOO_LOCALE."""
        conn = OdooConnection(config_with_locale)
        mock_proxy = _make_connected(conn)

        conn.execute_kw(
            "res.partner",
            "search_read",
            [[]],
            {"context": {"lang": "de_DE"}},
        )

        passed_kwargs = mock_proxy.execute_kw.call_args[0][6]
        assert passed_kwargs["context"]["lang"] == "de_DE"

    def test_locale_works_through_convenience_methods(self, config_with_locale):
        """Locale should be injected when using search/read/search_read helpers."""
        conn = OdooConnection(config_with_locale)
        mock_proxy = _make_connected(conn)

        conn.search_read("res.partner", [["is_company", "=", True]], fields=["name"])

        passed_kwargs = mock_proxy.execute_kw.call_args[0][6]
        assert passed_kwargs["context"]["lang"] == "es_ES"

    def test_locale_does_not_mutate_shared_kwargs(self, config_with_locale):
        """Ensure locale injection doesn't leak between calls via shared dicts."""
        conn = OdooConnection(config_with_locale)
        _make_connected(conn)

        shared_kwargs = {"limit": 5}
        conn.execute_kw("res.partner", "search", [[]], shared_kwargs)

        conn2 = OdooConnection(
            OdooConfig(
                url="https://test.odoo.com",
                api_key="test_key",
                username="test",
                database="test_db",
                yolo_mode="true",
            )
        )
        mock_proxy2 = _make_connected(conn2)
        fresh_kwargs = {"limit": 5}
        conn2.execute_kw("res.partner", "search", [[]], fresh_kwargs)

        passed_kwargs2 = mock_proxy2.execute_kw.call_args[0][6]
        assert "context" not in passed_kwargs2 or "lang" not in passed_kwargs2.get("context", {})


class TestLocaleInvalidFallback:
    def test_invalid_locale_falls_back_and_retries(self, config_with_locale):
        """Odoo rejects invalid locale → disable locale, retry succeeds."""
        conn = OdooConnection(config_with_locale)
        mock_proxy = _make_connected(conn)

        fault = xmlrpc.client.Fault(1, "Invalid language code: es_ES")
        mock_proxy.execute_kw.side_effect = [fault, [{"id": 1, "name": "Test"}]]

        result = conn.execute_kw("res.partner", "search_read", [[]], {})

        assert result == [{"id": 1, "name": "Test"}]
        assert conn.config.locale is None
        assert mock_proxy.execute_kw.call_count == 2

    def test_retry_does_not_include_lang(self, config_with_locale):
        """After fallback, the retry call should not have lang in context."""
        conn = OdooConnection(config_with_locale)
        mock_proxy = _make_connected(conn)

        fault = xmlrpc.client.Fault(1, "Invalid language code: es_ES")
        mock_proxy.execute_kw.side_effect = [fault, []]

        conn.execute_kw("res.partner", "search", [[]], {})

        # Second call (the retry) should not have lang
        retry_kwargs = mock_proxy.execute_kw.call_args_list[1][0][6]
        assert "lang" not in retry_kwargs.get("context", {})

    def test_other_faults_still_raise(self, config_with_locale):
        """Non-locale faults should propagate as OdooConnectionError."""
        conn = OdooConnection(config_with_locale)
        mock_proxy = _make_connected(conn)

        fault = xmlrpc.client.Fault(2, "Access denied")
        mock_proxy.execute_kw.side_effect = fault

        with pytest.raises(OdooConnectionError, match="Operation failed"):
            conn.execute_kw("res.partner", "search", [[]], {})

        # Locale should NOT be disabled for unrelated faults
        assert conn.config.locale == "es_ES"


class TestLocaleConfig:
    def test_locale_from_env(self):
        with patch.dict(
            "os.environ",
            {
                "ODOO_URL": "https://test.odoo.com",
                "ODOO_USER": "test",
                "ODOO_PASSWORD": "test",
                "ODOO_LOCALE": "fr_FR",
                "ODOO_YOLO": "true",
            },
        ):
            config = load_config()
            assert config.locale == "fr_FR"

    def test_no_locale_by_default(self):
        with patch.dict(
            "os.environ",
            {
                "ODOO_URL": "https://test.odoo.com",
                "ODOO_USER": "test",
                "ODOO_PASSWORD": "test",
                "ODOO_YOLO": "true",
            },
            clear=True,
        ):
            config = load_config()
            assert config.locale is None

    def test_empty_locale_treated_as_none(self):
        with patch.dict(
            "os.environ",
            {
                "ODOO_URL": "https://test.odoo.com",
                "ODOO_USER": "test",
                "ODOO_PASSWORD": "test",
                "ODOO_LOCALE": "  ",
                "ODOO_YOLO": "true",
            },
        ):
            config = load_config()
            assert config.locale is None
