# #region Test.SupersetClient.Charts [C:3] [TYPE Module] [SEMANTICS test,superset,chart,database]
# @BRIEF Unit tests for chart and database mixins — get, list, layout extraction, CRUD.
# @RELATION BINDS_TO -> [SupersetChartsMixin]
# @RELATION BINDS_TO -> [SupersetDatabasesMixin]
# @TEST_EDGE: empty_charts -> get_charts returns (0, [])
# @TEST_EDGE: unknown_chart_id_key -> _extract_chart_ids_from_layout skips unparseable values
# @TEST_EDGE: empty_databases -> get_databases returns (0, [])
# @TEST_EDGE: database_uuid_not_found -> get_database_by_uuid returns None
from unittest.mock import AsyncMock, MagicMock

import pytest

from src.core.config_models import Environment
from src.core.superset_client import SupersetClient


def _make_env() -> Environment:
    return Environment(id="env1", name="T", url="http://test.local", username="u", password="p")


def _make_client() -> SupersetClient:
    env = _make_env()
    client = SupersetClient(env)
    mc = MagicMock()
    mc.request = AsyncMock(return_value={})
    mc.fetch_paginated_data = AsyncMock(return_value=[])
    client.client = mc
    client.network = mc
    return client


# ── Charts: get_chart ───────────────────────────────────────────────────────

# #region test_get_chart_success [C:2] [TYPE Function]
# @BRIEF get_chart returns the response dict for a valid chart ID.
@pytest.mark.asyncio
async def test_get_chart_success():
    client = _make_client()
    expected = {"result": {"id": 7, "slice_name": "Revenue"}}
    client.client.request = AsyncMock(return_value=expected)
    result = await client.get_chart(7)
    assert result == expected
    client.client.request.assert_awaited_once_with(method="GET", endpoint="/chart/7")
# #endregion test_get_chart_success

# #region test_get_chart_not_found [C:2] [TYPE Function]
# @BRIEF get_chart propagates error when chart does not exist (404 from API).
@pytest.mark.asyncio
async def test_get_chart_not_found():
    client = _make_client()
    client.client.request = AsyncMock(side_effect=Exception("404 Not Found"))
    with pytest.raises(Exception, match="404"):
        await client.get_chart(9999)
# #endregion test_get_chart_not_found

# ── Charts: get_charts ──────────────────────────────────────────────────────

# #region test_get_charts_success [C:2] [TYPE Function]
# @BRIEF get_charts returns (count, data) from paginated fetch.
@pytest.mark.asyncio
async def test_get_charts_success():
    client = _make_client()
    client.client.fetch_paginated_data = AsyncMock(return_value=[
        {"id": 1, "slice_name": "A"}, {"id": 2, "slice_name": "B"},
    ])
    count, data = await client.get_charts()
    assert count == 2
    assert data[0]["slice_name"] == "A"
# #endregion test_get_charts_success

# #region test_get_charts_empty [C:2] [TYPE Function]
# @BRIEF get_charts returns (0, []) when no charts exist.
@pytest.mark.asyncio
async def test_get_charts_empty():
    client = _make_client()
    client.client.fetch_paginated_data = AsyncMock(return_value=[])
    count, data = await client.get_charts()
    assert count == 0
    assert data == []
# #endregion test_get_charts_empty

# #region test_get_charts_custom_columns [C:2] [TYPE Function]
# @BRIEF get_charts passes custom columns through to pagination query.
@pytest.mark.asyncio
async def test_get_charts_custom_columns():
    client = _make_client()
    client.client.fetch_paginated_data = AsyncMock(return_value=[{"id": 1}])
    await client.get_charts(query={"columns": ["id", "slice_name", "viz_type"]})
    call_kwargs = client.client.fetch_paginated_data.call_args
    base_query = call_kwargs.kwargs["pagination_options"]["base_query"]
    assert base_query["columns"] == ["id", "slice_name", "viz_type"]
# #endregion test_get_charts_custom_columns

# ── Charts: _extract_chart_ids_from_layout ──────────────────────────────────

# #region test_extract_chart_ids_chartId_key [C:2] [TYPE Function]
# @BRIEF _extract_chart_ids_from_layout finds IDs via 'chartId' key.
def test_extract_chart_ids_chartId_key():
    client = _make_client()
    layout = {"row1": {"chartId": 10}, "row2": {"chartId": 20}}
    ids = client._extract_chart_ids_from_layout(layout)
    assert ids == {10, 20}
# #endregion test_extract_chart_ids_chartId_key

# #region test_extract_chart_ids_chart_id_key [C:2] [TYPE Function]
# @BRIEF _extract_chart_ids_from_layout finds IDs via 'chart_id' key.
def test_extract_chart_ids_chart_id_key():
    client = _make_client()
    layout = {"a": {"chart_id": 5}}
    ids = client._extract_chart_ids_from_layout(layout)
    assert ids == {5}
# #endregion test_extract_chart_ids_chart_id_key

# #region test_extract_chart_ids_slice_id_key [C:2] [TYPE Function]
# @BRIEF _extract_chart_ids_from_layout finds IDs via 'slice_id' and 'sliceId' keys.
def test_extract_chart_ids_slice_id_key():
    client = _make_client()
    layout = {"x": {"slice_id": 3}, "y": {"sliceId": 4}}
    ids = client._extract_chart_ids_from_layout(layout)
    assert ids == {3, 4}
# #endregion test_extract_chart_ids_slice_id_key

# #region test_extract_chart_ids_CHART_dash_N_pattern [C:2] [TYPE Function]
# @BRIEF _extract_chart_ids_from_layout extracts numeric IDs from 'CHART-N' string ids.
def test_extract_chart_ids_CHART_dash_N_pattern():
    client = _make_client()
    layout = {"a": {"id": "CHART-42"}, "b": {"id": "CHART-7"}, "c": {"id": "OTHER-1"}}
    ids = client._extract_chart_ids_from_layout(layout)
    assert ids == {42, 7}
# #endregion test_extract_chart_ids_CHART_dash_N_pattern

# #region test_extract_chart_ids_nested [C:2] [TYPE Function]
# @BRIEF _extract_chart_ids_from_layout traverses deeply nested structures.
def test_extract_chart_ids_nested():
    client = _make_client()
    layout = {
        "rows": [
            {"cols": [{"chartId": 1}, {"chartId": 2}]},
            {"cols": [{"chartId": 3}]},
        ]
    }
    ids = client._extract_chart_ids_from_layout(layout)
    assert ids == {1, 2, 3}
# #endregion test_extract_chart_ids_nested

# #region test_extract_chart_ids_empty [C:2] [TYPE Function]
# @BRIEF _extract_chart_ids_from_layout returns empty set for empty payload.
def test_extract_chart_ids_empty():
    client = _make_client()
    assert client._extract_chart_ids_from_layout({}) == set()
    assert client._extract_chart_ids_from_layout([]) == set()
# #endregion test_extract_chart_ids_empty

# #region test_extract_chart_ids_unparseable [C:2] [TYPE Function]
# @BRIEF _extract_chart_ids_from_layout skips unparseable chart ID values.
def test_extract_chart_ids_unparseable():
    client = _make_client()
    layout = {"a": {"chartId": "not_a_number"}, "b": {"chartId": 99}}
    ids = client._extract_chart_ids_from_layout(layout)
    assert ids == {99}
# #endregion test_extract_chart_ids_unparseable

# #region test_extract_chart_ids_non_dict_input [C:2] [TYPE Function]
# @BRIEF _extract_chart_ids_from_layout handles scalar input gracefully.
def test_extract_chart_ids_non_dict_input():
    client = _make_client()
    assert client._extract_chart_ids_from_layout("string") == set()
    assert client._extract_chart_ids_from_layout(42) == set()
    assert client._extract_chart_ids_from_layout(None) == set()
# #endregion test_extract_chart_ids_non_dict_input

# ── Databases: get_databases ────────────────────────────────────────────────

# #region test_get_databases_success [C:2] [TYPE Function]
# @BRIEF get_databases returns (count, data) from paginated fetch.
@pytest.mark.asyncio
async def test_get_databases_success():
    client = _make_client()
    client.client.fetch_paginated_data = AsyncMock(return_value=[
        {"id": 1, "database_name": "pg"}, {"id": 2, "database_name": "mysql"},
    ])
    count, data = await client.get_databases()
    assert count == 2
    assert data[0]["database_name"] == "pg"
# #endregion test_get_databases_success

# #region test_get_databases_empty [C:2] [TYPE Function]
# @BRIEF get_databases returns (0, []) when no databases exist.
@pytest.mark.asyncio
async def test_get_databases_empty():
    client = _make_client()
    client.client.fetch_paginated_data = AsyncMock(return_value=[])
    count, data = await client.get_databases()
    assert count == 0
    assert data == []
# #endregion test_get_databases_empty

# ── Databases: get_database ─────────────────────────────────────────────────

# #region test_get_database_success [C:2] [TYPE Function]
# @BRIEF get_database returns response dict for a valid database ID.
@pytest.mark.asyncio
async def test_get_database_success():
    client = _make_client()
    expected = {"id": 3, "database_name": "pg_main", "backend": "postgresql"}
    client.client.request = AsyncMock(return_value=expected)
    result = await client.get_database(3)
    assert result == expected
    client.client.request.assert_awaited_once_with(method="GET", endpoint="/database/3")
# #endregion test_get_database_success

# #region test_get_database_not_found [C:2] [TYPE Function]
# @BRIEF get_database propagates error for non-existent database ID.
@pytest.mark.asyncio
async def test_get_database_not_found():
    client = _make_client()
    client.client.request = AsyncMock(side_effect=Exception("404 Not Found"))
    with pytest.raises(Exception, match="404"):
        await client.get_database(9999)
# #endregion test_get_database_not_found

# ── Databases: get_databases_summary ────────────────────────────────────────

# #region test_get_databases_summary [C:2] [TYPE Function]
# @BRIEF get_databases_summary renames 'backend' key to 'engine'.
@pytest.mark.asyncio
async def test_get_databases_summary():
    client = _make_client()
    client.client.fetch_paginated_data = AsyncMock(return_value=[
        {"id": 1, "uuid": "abc", "database_name": "pg", "backend": "postgresql"},
    ])
    result = await client.get_databases_summary()
    assert len(result) == 1
    assert result[0]["engine"] == "postgresql"
    assert "backend" not in result[0]
# #endregion test_get_databases_summary

# #region test_get_databases_summary_empty [C:2] [TYPE Function]
# @BRIEF get_databases_summary returns empty list when no databases.
@pytest.mark.asyncio
async def test_get_databases_summary_empty():
    client = _make_client()
    client.client.fetch_paginated_data = AsyncMock(return_value=[])
    result = await client.get_databases_summary()
    assert result == []
# #endregion test_get_databases_summary_empty

# ── Databases: get_database_by_uuid ─────────────────────────────────────────

# #region test_get_database_by_uuid_found [C:2] [TYPE Function]
# @BRIEF get_database_by_uuid returns the first match.
@pytest.mark.asyncio
async def test_get_database_by_uuid_found():
    client = _make_client()
    client.client.fetch_paginated_data = AsyncMock(return_value=[
        {"id": 5, "uuid": "target-uuid", "database_name": "found"},
    ])
    result = await client.get_database_by_uuid("target-uuid")
    assert result["id"] == 5
# #endregion test_get_database_by_uuid_found

# #region test_get_database_by_uuid_not_found [C:2] [TYPE Function]
# @BRIEF get_database_by_uuid returns None when no match.
@pytest.mark.asyncio
async def test_get_database_by_uuid_not_found():
    client = _make_client()
    client.client.fetch_paginated_data = AsyncMock(return_value=[])
    result = await client.get_database_by_uuid("nonexistent-uuid")
    assert result is None
# #endregion test_get_database_by_uuid_not_found

# ── Databases: create_database ──────────────────────────────────────────────

# #region test_create_database_success [C:2] [TYPE Function]
# @BRIEF create_database sends POST with correct payload and returns response.
@pytest.mark.asyncio
async def test_create_database_success():
    client = _make_client()
    client.client.request = AsyncMock(return_value={"id": 10, "result": "created"})
    result = await client.create_database("mydb", "postgresql://localhost/mydb")
    assert result["id"] == 10
    call_kwargs = client.client.request.call_args
    assert call_kwargs.kwargs["method"] == "POST"
    assert call_kwargs.kwargs["endpoint"] == "/database/"
    data = call_kwargs.kwargs["data"]
    assert data["database_name"] == "mydb"
    assert data["sqlalchemy_uri"] == "postgresql://localhost/mydb"
    assert data["expose_in_sqllab"] is True
    assert data["allow_dml"] is False
# #endregion test_create_database_success

# #region test_create_database_custom_flags [C:2] [TYPE Function]
# @BRIEF create_database passes custom expose_in_sqllab and allow_dml flags.
@pytest.mark.asyncio
async def test_create_database_custom_flags():
    client = _make_client()
    client.client.request = AsyncMock(return_value={"id": 11})
    await client.create_database("db2", "sqlite:///test.db", expose_in_sqllab=False, allow_dml=True)
    data = client.client.request.call_args.kwargs["data"]
    assert data["expose_in_sqllab"] is False
    assert data["allow_dml"] is True
# #endregion test_create_database_custom_flags

# ── Databases: delete_database ──────────────────────────────────────────────

# #region test_delete_database_success [C:2] [TYPE Function]
# @BRIEF delete_database sends DELETE request and returns response.
@pytest.mark.asyncio
async def test_delete_database_success():
    client = _make_client()
    client.client.request = AsyncMock(return_value={"result": "deleted"})
    result = await client.delete_database(7)
    assert result == {"result": "deleted"}
    client.client.request.assert_awaited_once_with(method="DELETE", endpoint="/database/7")
# #endregion test_delete_database_success

# #region test_delete_database_not_found [C:2] [TYPE Function]
# @BRIEF delete_database propagates error for non-existent database.
@pytest.mark.asyncio
async def test_delete_database_not_found():
    client = _make_client()
    client.client.request = AsyncMock(side_effect=Exception("404"))
    with pytest.raises(Exception, match="404"):
        await client.delete_database(9999)
# #endregion test_delete_database_not_found

# #endregion Test.SupersetClient.Charts
