# #region Test.DashboardsWriteMixin [C:3] [TYPE Module] [SEMANTICS test, dashboard, write, markdown, layout]
# @BRIEF Tests for core/superset_client/_dashboards_write.py — SupersetDashboardsWriteMixin.
# @RELATION BINDS_TO -> [SupersetDashboardsWriteMixin]

from pathlib import Path
import sys

sys.path.insert(0, str(Path(__file__).parent.parent.parent / "src"))

from unittest.mock import AsyncMock, MagicMock, call, patch

import pytest


class TestDashboardsWriteMixin:
    """Test all methods of SupersetDashboardsWriteMixin."""

    @pytest.fixture
    def mixin(self):
        from src.core.superset_client._dashboards_write import (
            SupersetDashboardsWriteMixin,
        )

        m = SupersetDashboardsWriteMixin()
        m.client = MagicMock()
        m.client.request = AsyncMock()
        return m

    # ── create_markdown_chart ──

    @pytest.mark.asyncio
    async def test_create_markdown_chart_success(self, mixin):
        mixin._resolve_markdown_datasource = AsyncMock(return_value=10)
        mixin.client.request.return_value = {"id": 42}
        result = await mixin.create_markdown_chart(1, "Hello")
        assert result == 42
        mixin.client.request.assert_awaited_once()
        call_kwargs = mixin.client.request.call_args
        assert call_kwargs[1]["method"] == "POST"
        assert call_kwargs[1]["endpoint"] == "/chart/"

    @pytest.mark.asyncio
    async def test_create_markdown_chart_id_in_result(self, mixin):
        mixin._resolve_markdown_datasource = AsyncMock(return_value=10)
        mixin.client.request.return_value = {"result": {"id": 99}}
        result = await mixin.create_markdown_chart(1, "Banner")
        assert result == 99

    @pytest.mark.asyncio
    async def test_create_markdown_chart_no_id_returns_zero(self, mixin):
        mixin._resolve_markdown_datasource = AsyncMock(return_value=10)
        mixin.client.request.return_value = {}
        result = await mixin.create_markdown_chart(1, "Text")
        assert result == 0

    @pytest.mark.asyncio
    async def test_create_markdown_chart_request_fails(self, mixin):
        mixin._resolve_markdown_datasource = AsyncMock(return_value=10)
        mixin.client.request.side_effect = ValueError("API error")
        with pytest.raises(ValueError):
            await mixin.create_markdown_chart(1, "Fail")

    # ── update_markdown_chart ──

    @pytest.mark.asyncio
    async def test_update_markdown_chart_success(self, mixin):
        mixin.client.request.return_value = {"result": "ok"}
        result = await mixin.update_markdown_chart(5, "Updated text")
        mixin.client.request.assert_awaited_once_with(
            method="PUT", endpoint="/chart/5", data=mixin.client.request.call_args[1]["data"]
        )
        assert result == {"result": "ok"}

    @pytest.mark.asyncio
    async def test_update_markdown_chart_fails(self, mixin):
        mixin.client.request.side_effect = RuntimeError("Update failed")
        with pytest.raises(RuntimeError):
            await mixin.update_markdown_chart(5, "Text")

    # ── update_dashboard_layout ──

    @pytest.mark.asyncio
    async def test_update_dashboard_layout_success(self, mixin):
        mock_position = {
            "GRID_ID": {"children": []},
            "DASHBOARD_VERSION_KEY": "v2",
        }
        # First call returns dashboard, second call returns success
        mixin.client.request = AsyncMock(
            side_effect=[
                {"result": {"position_json": '{"GRID_ID": {"children": []}, "DASHBOARD_VERSION_KEY": "v2"}'}},
                {"result": "ok"},
            ]
        )
        result = await mixin.update_dashboard_layout(1, 42)
        assert mixin.client.request.call_count == 2
        assert result == {"result": "ok"}

    @pytest.mark.asyncio
    async def test_update_dashboard_layout_fails(self, mixin):
        mixin.client.request.side_effect = ConnectionError("Network error")
        with pytest.raises(ConnectionError):
            await mixin.update_dashboard_layout(1, 42)

    # ── remove_chart_from_layout ──

    @pytest.mark.asyncio
    async def test_remove_chart_from_layout_success(self, mixin):
        mixin.client.request = AsyncMock(
            side_effect=[
                {"result": {"position_json": '{"GRID_ID": {"children": ["ROW-banner-42"]}, "ROW-banner-42": {}, "MARKDOWN-banner-42": {}}'}},
                {"result": "ok"},
            ]
        )
        result = await mixin.remove_chart_from_layout(1, 42)
        assert mixin.client.request.call_count == 2
        assert result == {"result": "ok"}

    @pytest.mark.asyncio
    async def test_remove_chart_from_layout_fails(self, mixin):
        mixin.client.request.side_effect = OSError("IO error")
        with pytest.raises(OSError):
            await mixin.remove_chart_from_layout(1, 42)

    # ── delete_chart ──

    @pytest.mark.asyncio
    async def test_delete_chart_success(self, mixin):
        mixin.client.request.return_value = {"result": "deleted"}
        result = await mixin.delete_chart(42)
        mixin.client.request.assert_awaited_once_with(method="DELETE", endpoint="/chart/42")
        assert result == {"result": "deleted"}

    @pytest.mark.asyncio
    async def test_delete_chart_fails(self, mixin):
        mixin.client.request.side_effect = PermissionError("Forbidden")
        with pytest.raises(PermissionError):
            await mixin.delete_chart(42)

    # ── update_banner_on_dashboard ──

    @pytest.mark.asyncio
    async def test_update_banner_on_dashboard_success(self, mixin):
        mixin.client.request = AsyncMock(
            side_effect=[
                {"result": {"position_json": '{"MARKDOWN-banner-7": {"meta": {"code": "old", "height": 19}}}'}},
                {"result": "ok"},
            ]
        )
        result = await mixin.update_banner_on_dashboard(1, 7, "New content")
        assert mixin.client.request.call_count == 2
        assert result == {"result": "ok"}

    @pytest.mark.asyncio
    async def test_update_banner_on_dashboard_fails(self, mixin):
        mixin.client.request.side_effect = TimeoutError("Timed out")
        with pytest.raises(TimeoutError):
            await mixin.update_banner_on_dashboard(1, 7, "Content")

    # ── get_dashboard_layout ──

    @pytest.mark.asyncio
    async def test_get_dashboard_layout_success(self, mixin):
        mixin.client.request.return_value = {
            "result": {"position_json": '{"GRID_ID": {"children": []}}'}
        }
        result = await mixin.get_dashboard_layout(1)
        assert "GRID_ID" in result
        assert result["GRID_ID"]["children"] == []

    @pytest.mark.asyncio
    async def test_get_dashboard_layout_fails(self, mixin):
        mixin.client.request.side_effect = Exception("Fetch failed")
        with pytest.raises(Exception):
            await mixin.get_dashboard_layout(1)

    # ── _resolve_markdown_datasource ──

    @pytest.mark.asyncio
    async def test_resolve_markdown_datasource_from_dashboard(self, mixin):
        mixin.client.request.return_value = {"result": [{"id": 5}]}
        result = await mixin._resolve_markdown_datasource(1)
        assert result == 5

    @pytest.mark.asyncio
    async def test_resolve_markdown_datasource_fallback_to_global(self, mixin):
        mixin.client.request = AsyncMock(
            side_effect=[
                Exception("Dashboard datasets failed"),
                {"result": [{"id": 99}]},
            ]
        )
        result = await mixin._resolve_markdown_datasource(1)
        assert result == 99

    @pytest.mark.asyncio
    async def test_resolve_markdown_datasource_both_fail(self, mixin):
        mixin.client.request = AsyncMock(
            side_effect=[
                Exception("Dashboard failed"),
                Exception("Global failed"),
            ]
        )
        with pytest.raises(RuntimeError, match="Cannot create markdown chart"):
            await mixin._resolve_markdown_datasource(1)

    @pytest.mark.asyncio
    async def test_resolve_markdown_datasource_empty_dashboard_datasets(self, mixin):
        mixin.client.request.return_value = {"result": []}
        # Then fallback to global
        mixin.client.request = AsyncMock(
            side_effect=[
                {"result": []},
                {"result": [{"id": 77}]},
            ]
        )
        result = await mixin._resolve_markdown_datasource(1)
        assert result == 77

    @pytest.mark.asyncio
    async def test_resolve_markdown_datasource_dashboard_has_no_id_field(self, mixin):
        mixin.client.request.return_value = {"result": [{"name": "test"}]}
        mixin.client.request = AsyncMock(
            side_effect=[
                {"result": [{"name": "test"}]},
                {"result": [{"id": 88}]},
            ]
        )
        result = await mixin._resolve_markdown_datasource(1)
        assert result == 88
# #endregion Test.DashboardsWriteMixin
