# #region Test.Api.DashboardDetailRoutes [C:3] [TYPE Module] [SEMANTICS test,dashboard,detail,api]
# @BRIEF Unit tests for dashboard detail routes — db-mappings, detail, tasks history, thumbnail.
# @RELATION BINDS_TO -> [DashboardDetailRoutes]
# @TEST_EDGE: source_env_not_found -> 404
# @TEST_EDGE: target_env_not_found -> 404
# @TEST_EDGE: db_mapping_fail -> 503
# @TEST_EDGE: dashboard_not_found -> 503
# @TEST_EDGE: env_not_found_detail -> 404

import os

os.environ.setdefault("DATABASE_URL", "sqlite:///:memory:")
os.environ.setdefault("AUTH_DATABASE_URL", "sqlite:///:memory:")
os.environ.setdefault("SECRET_KEY", "test-secret-key-for-tests")

import sys
from pathlib import Path
from unittest.mock import AsyncMock, MagicMock, patch

import pytest
from fastapi import FastAPI
from fastapi.testclient import TestClient

_src = str(Path(__file__).resolve().parent.parent.parent / "src")
if _src not in sys.path:
    sys.path.insert(0, _src)


def _make_mock_env(id: str = "env-1", name: str = "TestEnv"):
    env = MagicMock()
    env.id = id
    env.name = name
    env.url = "https://superset.example.com"
    env.username = "admin"
    env.password = "pass"
    env.verify_ssl = True
    return env


def _make_client(overrides: dict | None = None) -> TestClient:
    from src.api.routes.dashboards._detail_routes import router
    from src.dependencies import get_config_manager, get_current_user, get_mapping_service, get_task_manager, has_permission
    from src.schemas.auth import User, RoleSchema

    app = FastAPI()
    app.include_router(router)

    mock_user = User(
        id="admin-1", username="admin", email="admin@x.com",
        auth_source="LOCAL",
        created_at=__import__("datetime").datetime.now(),
        roles=[RoleSchema(id="r1", name="Admin", description="", permissions=[])],
    )

    defaults = {
        get_config_manager: lambda: MagicMock(),
        get_mapping_service: lambda: MagicMock(),
        get_task_manager: lambda: MagicMock(),
        get_current_user: lambda: mock_user,
    }
    if overrides:
        defaults.update(overrides)
    for dep, mock_fn in defaults.items():
        app.dependency_overrides[dep] = mock_fn
    return TestClient(app)


# ── get_database_mappings ──

class TestGetDatabaseMappings:
    """GET /api/dashboards/db-mappings"""

    def test_mappings_success(self):
        mock_config = MagicMock()
        mock_config.get_environments.return_value = [
            _make_mock_env(id="src-env"),
            _make_mock_env(id="tgt-env"),
        ]
        mock_mapping = AsyncMock()
        mock_mapping.get_suggestions.return_value = [
            {"source_db": "db1", "target_db": "db2", "source_db_uuid": "u1", "target_db_uuid": "u2", "confidence": 0.95},
        ]

        from src.dependencies import get_config_manager, get_mapping_service
        client = _make_client({
            get_config_manager: lambda: mock_config,
            get_mapping_service: lambda: mock_mapping,
        })
        resp = client.get("/api/dashboards/db-mappings?source_env_id=src-env&target_env_id=tgt-env")
        assert resp.status_code == 200
        data = resp.json()
        assert len(data["mappings"]) == 1
        assert data["mappings"][0]["source_db"] == "db1"

    def test_mappings_source_not_found(self):
        mock_config = MagicMock()
        mock_config.get_environments.return_value = [_make_mock_env(id="tgt-env")]

        from src.dependencies import get_config_manager
        client = _make_client({get_config_manager: lambda: mock_config})
        resp = client.get("/api/dashboards/db-mappings?source_env_id=src-ghost&target_env_id=tgt-env")
        assert resp.status_code == 404
        assert "Source environment not found" in resp.text

    def test_mappings_target_not_found(self):
        mock_config = MagicMock()
        mock_config.get_environments.return_value = [_make_mock_env(id="src-env")]

        from src.dependencies import get_config_manager
        client = _make_client({get_config_manager: lambda: mock_config})
        resp = client.get("/api/dashboards/db-mappings?source_env_id=src-env&target_env_id=tgt-ghost")
        assert resp.status_code == 404
        assert "Target environment not found" in resp.text

    def test_mappings_service_fail(self):
        mock_config = MagicMock()
        mock_config.get_environments.return_value = [
            _make_mock_env(id="src-env"),
            _make_mock_env(id="tgt-env"),
        ]
        mock_mapping = AsyncMock()
        mock_mapping.get_suggestions.side_effect = Exception("Service unavailable")

        from src.dependencies import get_config_manager, get_mapping_service
        client = _make_client({
            get_config_manager: lambda: mock_config,
            get_mapping_service: lambda: mock_mapping,
        })
        resp = client.get("/api/dashboards/db-mappings?source_env_id=src-env&target_env_id=tgt-env")
        assert resp.status_code == 503
        assert "Failed to get database mappings" in resp.text


# ── get_dashboard_detail ──

class TestGetDashboardDetail:
    """GET /api/dashboards/{dashboard_ref}"""

    def test_detail_success(self):
        mock_config = MagicMock()
        mock_config.get_environments.return_value = [_make_mock_env(id="env-1")]

        mock_client = AsyncMock()
        mock_client.get_dashboards_page.return_value = (1, [{"id": 42}])
        mock_client.get_dashboard_detail.return_value = {
            "id": 42,
            "title": "Test Dash",
            "chart_count": 5,
            "dataset_count": 3,
            "charts": [{"id": 1, "title": "Chart 1", "viz_type": "line"}],
            "datasets": [{"id": 1, "table_name": "sales", "database": "prod"}],
        }

        from src.dependencies import get_config_manager

        with patch("src.api.routes.dashboards._detail_routes.SupersetClient", return_value=mock_client), \
             patch("src.api.routes.dashboards._detail_routes.get_superset_client") as mock_reg:
            mock_reg.return_value = AsyncMock()
            client = _make_client({get_config_manager: lambda: mock_config})
            resp = client.get("/api/dashboards/42?env_id=env-1")
        assert resp.status_code == 200
        data = resp.json()
        assert data["id"] == 42

    def test_detail_env_not_found(self):
        mock_config = MagicMock()
        mock_config.get_environments.return_value = []

        from src.dependencies import get_config_manager
        client = _make_client({get_config_manager: lambda: mock_config})
        resp = client.get("/api/dashboards/42?env_id=env-ghost")
        assert resp.status_code == 404
        assert "Environment not found" in resp.text

    def test_detail_service_error_503(self):
        mock_config = MagicMock()
        mock_config.get_environments.return_value = [_make_mock_env(id="env-1")]

        mock_client = AsyncMock()
        mock_client.get_dashboard_detail.side_effect = Exception("Superset API error")
        mock_client.get_dashboards_page.return_value = (1, [{"id": 42}])

        from src.dependencies import get_config_manager

        with patch("src.api.routes.dashboards._detail_routes.SupersetClient", return_value=mock_client), \
             patch("src.api.routes.dashboards._detail_routes.get_superset_client") as mock_reg:
            mock_reg.return_value = AsyncMock()
            client = _make_client({get_config_manager: lambda: mock_config})
            resp = client.get("/api/dashboards/42?env_id=env-1")
        assert resp.status_code == 503
        assert "Failed to fetch dashboard detail" in resp.text


# ── get_dashboard_tasks_history ──

class TestGetDashboardTasksHistory:
    """GET /api/dashboards/{dashboard_ref}/tasks"""

    def test_tasks_history_success(self):
        mock_config = MagicMock()
        mock_config.get_environments.return_value = [_make_mock_env(id="env-1")]

        mock_task = MagicMock()
        mock_task.id = "task-1"
        mock_task.plugin_id = "superset-backup"
        mock_task.status = "SUCCESS"
        mock_task.params = {"dashboards": ["42"], "environment_id": "env-1"}
        mock_task.result = {"status": "ok", "summary": "Backup done"}
        mock_task.started_at = __import__("datetime").datetime(2024, 1, 1)
        mock_task.finished_at = __import__("datetime").datetime(2024, 1, 1, 0, 1)

        mock_task_manager = MagicMock()
        mock_task_manager.get_all_tasks.return_value = [mock_task]

        from src.dependencies import get_config_manager, get_task_manager
        client = _make_client({
            get_config_manager: lambda: mock_config,
            get_task_manager: lambda: mock_task_manager,
        })
        resp = client.get("/api/dashboards/42/tasks?env_id=env-1")
        assert resp.status_code == 200
        data = resp.json()
        assert data["dashboard_id"] == 42
        assert len(data["items"]) == 1
        assert data["items"][0]["id"] == "task-1"

    def test_tasks_history_empty(self):
        mock_config = MagicMock()
        mock_config.get_environments.return_value = [_make_mock_env(id="env-1")]

        mock_task_manager = MagicMock()
        mock_task_manager.get_all_tasks.return_value = []

        from src.dependencies import get_config_manager, get_task_manager
        client = _make_client({
            get_config_manager: lambda: mock_config,
            get_task_manager: lambda: mock_task_manager,
        })
        resp = client.get("/api/dashboards/42/tasks?env_id=env-1")
        assert resp.status_code == 200
        assert resp.json()["items"] == []

    def test_tasks_history_slug_no_env_id(self):
        mock_task_manager = MagicMock()

        from src.dependencies import get_config_manager, get_task_manager
        client = _make_client({
            get_config_manager: lambda: MagicMock(),
            get_task_manager: lambda: mock_task_manager,
        })
        resp = client.get("/api/dashboards/slug-ref/tasks")
        assert resp.status_code == 400
        assert "env_id is required" in resp.text

    def test_tasks_history_env_not_found(self):
        mock_config = MagicMock()
        mock_config.get_environments.return_value = []

        from src.dependencies import get_config_manager, get_task_manager
        client = _make_client({
            get_config_manager: lambda: mock_config,
            get_task_manager: lambda: MagicMock(),
        })
        resp = client.get("/api/dashboards/slug-ref/tasks?env_id=env-ghost")
        assert resp.status_code == 404
        assert "Environment not found" in resp.text


# ── get_dashboard_thumbnail ──

class TestGetDashboardThumbnail:
    """GET /api/dashboards/{dashboard_ref}/thumbnail"""

    def test_thumbnail_success(self):
        mock_config = MagicMock()
        mock_config.get_environments.return_value = [_make_mock_env(id="env-1")]

        mock_client = AsyncMock()
        mock_client.get_dashboards_page.return_value = (1, [{"id": 42}])

        mock_async_client = AsyncMock()

        async def _fake_request(method, endpoint, **kwargs):
            if "cache_dashboard_screenshot" in endpoint:
                return {"result": {"image_url": "/api/v1/dashboard/42/thumbnail/digest-1/"}}
            if "raw_response" in kwargs and kwargs["raw_response"]:
                mock_resp = MagicMock()
                mock_resp.status_code = 200
                mock_resp.headers = {"Content-Type": "image/png"}
                mock_resp.content = b"fake-image-bytes"
                return mock_resp
            return {}

        mock_async_client.request.side_effect = _fake_request
        mock_async_client.get_dashboards_page.return_value = (1, [{"id": 42}])

        from src.dependencies import get_config_manager

        with patch("src.api.routes.dashboards._detail_routes.SupersetClient", return_value=mock_client), \
             patch("src.api.routes.dashboards._detail_routes.get_superset_client", return_value=mock_async_client):
            client = _make_client({get_config_manager: lambda: mock_config})
            resp = client.get("/api/dashboards/42/thumbnail?env_id=env-1")
        assert resp.status_code == 200
        assert resp.content == b"fake-image-bytes"

    def test_thumbnail_env_not_found(self):
        mock_config = MagicMock()
        mock_config.get_environments.return_value = []

        from src.dependencies import get_config_manager
        client = _make_client({get_config_manager: lambda: mock_config})
        resp = client.get("/api/dashboards/42/thumbnail?env_id=env-ghost")
        assert resp.status_code == 404
        assert "Environment not found" in resp.text

    def test_thumbnail_202_accepted(self):
        """Superset returns 202 when thumbnail is being generated."""
        mock_config = MagicMock()
        mock_config.get_environments.return_value = [_make_mock_env(id="env-1")]

        mock_client = AsyncMock()
        mock_client.get_dashboards_page.return_value = (1, [{"id": 42}])

        mock_async_client = AsyncMock()

        async def _fake_request_202(method, endpoint, **kwargs):
            if "cache_dashboard_screenshot" in endpoint:
                return {"result": {"image_url": "/api/v1/dashboard/42/thumbnail/digest-1/"}}
            if "raw_response" in kwargs and kwargs["raw_response"]:
                mock_resp = MagicMock()
                mock_resp.status_code = 202
                mock_resp.headers = {"Content-Type": "application/json"}
                mock_resp.json.return_value = {"message": "Thumbnail is being generated"}
                return mock_resp
            return {}

        mock_async_client.request.side_effect = _fake_request_202

        from src.dependencies import get_config_manager

        with patch("src.api.routes.dashboards._detail_routes.SupersetClient", return_value=mock_client), \
             patch("src.api.routes.dashboards._detail_routes.get_superset_client", return_value=mock_async_client):
            client = _make_client({get_config_manager: lambda: mock_config})
            resp = client.get("/api/dashboards/42/thumbnail?env_id=env-1&force=true")
        assert resp.status_code == 202

    def test_thumbnail_dashboard_not_found_404(self):
        """DashboardNotFoundError returns 404."""
        from src.core.utils.network import DashboardNotFoundError
        mock_config = MagicMock()
        mock_config.get_environments.return_value = [_make_mock_env(id="env-1")]

        mock_client = AsyncMock()
        mock_client.get_dashboards_page.return_value = (1, [{"id": 42}])

        mock_async_client = AsyncMock()
        mock_async_client.request.side_effect = DashboardNotFoundError("Dashboard not found")

        from src.dependencies import get_config_manager

        with patch("src.api.routes.dashboards._detail_routes.SupersetClient", return_value=mock_client), \
             patch("src.api.routes.dashboards._detail_routes.get_superset_client", return_value=mock_async_client):
            client = _make_client({get_config_manager: lambda: mock_config})
            resp = client.get("/api/dashboards/42/thumbnail?env_id=env-1")
        assert resp.status_code == 404
        assert "Dashboard thumbnail not found" in resp.text

    def test_thumbnail_fallback_to_dashboard_url(self):
        """Fallback when cache_dashboard_screenshot is unavailable."""
        mock_config = MagicMock()
        mock_config.get_environments.return_value = [_make_mock_env(id="env-1")]

        mock_client = AsyncMock()
        mock_client.get_dashboards_page.return_value = (1, [{"id": 42}])

        mock_async_client = AsyncMock()

        async def _fake_fallback_request(method, endpoint, **kwargs):
            if "cache_dashboard_screenshot" in endpoint:
                return {}  # No image_url in response
            if endpoint == "/dashboard/42":
                return {"result": {"thumbnail_url": "/api/v1/dashboard/42/thumbnail/fallback-digest/"}}
            if "raw_response" in kwargs and kwargs["raw_response"]:
                mock_resp = MagicMock()
                mock_resp.status_code = 200
                mock_resp.headers = {"Content-Type": "image/png"}
                mock_resp.content = b"fallback-image"
                return mock_resp
            return {}

        mock_async_client.request.side_effect = _fake_fallback_request

        from src.dependencies import get_config_manager

        with patch("src.api.routes.dashboards._detail_routes.SupersetClient", return_value=mock_client), \
             patch("src.api.routes.dashboards._detail_routes.get_superset_client", return_value=mock_async_client):
            client = _make_client({get_config_manager: lambda: mock_config})
            resp = client.get("/api/dashboards/42/thumbnail?env_id=env-1")
        assert resp.status_code == 200
        assert resp.content == b"fallback-image"

    def test_thumbnail_generic_error_503(self):
        """Generic exception returns 503."""
        mock_config = MagicMock()
        mock_config.get_environments.return_value = [_make_mock_env(id="env-1")]

        mock_client = AsyncMock()
        mock_client.get_dashboards_page.return_value = (1, [{"id": 42}])

        mock_async_client = AsyncMock()
        mock_async_client.request.side_effect = RuntimeError("Unexpected error")

        from src.dependencies import get_config_manager

        with patch("src.api.routes.dashboards._detail_routes.SupersetClient", return_value=mock_client), \
             patch("src.api.routes.dashboards._detail_routes.get_superset_client", return_value=mock_async_client):
            client = _make_client({get_config_manager: lambda: mock_config})
            resp = client.get("/api/dashboards/42/thumbnail?env_id=env-1")
        assert resp.status_code == 503
        assert "Failed to fetch dashboard thumbnail" in resp.text
    def test_detail_http_exception_re_raised(self):
        """HTTPException from client is re-raised (line 158 except HTTPException)."""
        from fastapi import HTTPException as FastAPIHTTPException
        mock_config = MagicMock()
        mock_config.get_environments.return_value = [_make_mock_env(id="env-1")]

        mock_client = AsyncMock()
        mock_client.get_dashboards_page.return_value = (1, [{"id": 42}])
        mock_client.get_dashboard_detail.side_effect = FastAPIHTTPException(status_code=400, detail="Bad request")

        from src.dependencies import get_config_manager

        with patch("src.api.routes.dashboards._detail_routes.SupersetClient", return_value=mock_client), \
             patch("src.api.routes.dashboards._detail_routes.get_superset_client") as mock_reg:
            mock_reg.return_value = AsyncMock()
            client = _make_client({get_config_manager: lambda: mock_config})
            resp = client.get("/api/dashboards/42?env_id=env-1")
        assert resp.status_code == 400


# ── get_dashboard_tasks_history — slug ref path ──

class TestGetDashboardTasksHistorySlug:
    """GET /api/dashboards/{slug}/tasks with env_id — slug path (lines 202-203, 268)."""

    def test_tasks_history_slug_with_env(self):
        mock_config = MagicMock()
        mock_config.get_environments.return_value = [_make_mock_env(id="env-1")]

        mock_task = MagicMock()
        mock_task.id = "task-1"
        mock_task.plugin_id = "superset-backup"
        mock_task.status = "SUCCESS"
        mock_task.params = {"dashboard_ids": [42], "environment_id": "env-1"}
        mock_task.result = {"status": "ok", "summary": "Backup done"}
        mock_task.started_at = __import__("datetime").datetime(2024, 1, 1)
        mock_task.finished_at = __import__("datetime").datetime(2024, 1, 1, 0, 1)

        mock_task_manager = MagicMock()
        mock_task_manager.get_all_tasks.return_value = [mock_task]

        mock_client = AsyncMock()
        # _resolve_dashboard_id_from_ref_async calls get_dashboards_page
        mock_client.get_dashboards_page.return_value = (1, [{"id": 42}])

        from src.dependencies import get_config_manager, get_task_manager

        with patch("src.api.routes.dashboards._detail_routes.SupersetClient", return_value=mock_client):
            client = _make_client({
                get_config_manager: lambda: mock_config,
                get_task_manager: lambda: mock_task_manager,
            })
            resp = client.get("/api/dashboards/my-slug/tasks?env_id=env-1")
        assert resp.status_code == 200
        data = resp.json()
        assert data["dashboard_id"] == 42
        assert len(data["items"]) == 1


# ── get_dashboard_thumbnail — extra edge cases ──

class TestGetDashboardThumbnailExtended:
    """Additional thumbnail path coverage (lines 388-389, 400)."""

    def test_thumbnail_202_json_error(self):
        """202 response where JSON parsing fails returns default message (lines 388-389)."""
        mock_config = MagicMock()
        mock_config.get_environments.return_value = [_make_mock_env(id="env-1")]

        mock_client = AsyncMock()
        mock_client.get_dashboards_page.return_value = (1, [{"id": 42}])

        mock_async_client = AsyncMock()

        async def _fake_request(method, endpoint, **kwargs):
            if "cache_dashboard_screenshot" in endpoint:
                return {"result": {"image_url": "/api/v1/dashboard/42/thumbnail/digest-1/"}}
            if "raw_response" in kwargs and kwargs["raw_response"]:
                mock_resp = MagicMock()
                mock_resp.status_code = 202
                mock_resp.headers = {"Content-Type": "application/json"}
                mock_resp.json.side_effect = ValueError("Invalid JSON")
                return mock_resp
            return {}

        mock_async_client.request.side_effect = _fake_request

        from src.dependencies import get_config_manager

        with patch("src.api.routes.dashboards._detail_routes.SupersetClient", return_value=mock_client), \
             patch("src.api.routes.dashboards._detail_routes.get_superset_client", return_value=mock_async_client):
            client = _make_client({get_config_manager: lambda: mock_config})
            resp = client.get("/api/dashboards/42/thumbnail?env_id=env-1")
        assert resp.status_code == 202
        assert resp.json() == {"message": "Thumbnail is being generated"}

    def test_thumbnail_http_exception_re_raised(self):
        """HTTPException from get_superset_client is re-raised (line 400)."""
        from fastapi import HTTPException as FastAPIHTTPException
        mock_config = MagicMock()
        mock_config.get_environments.return_value = [_make_mock_env(id="env-1")]

        mock_client = AsyncMock()
        mock_client.get_dashboards_page.return_value = (1, [{"id": 42}])

        mock_gsc = AsyncMock()
        async def _raise_gsc(*a, **kw):
            raise FastAPIHTTPException(status_code=409, detail="Conflict")
        mock_gsc.side_effect = _raise_gsc

        from src.dependencies import get_config_manager

        with patch("src.api.routes.dashboards._detail_routes.SupersetClient", return_value=mock_client), \
             patch("src.api.routes.dashboards._detail_routes.get_superset_client", new=mock_gsc):
            client = _make_client({get_config_manager: lambda: mock_config})
            resp = client.get("/api/dashboards/42/thumbnail?env_id=env-1")
        assert resp.status_code == 409
# #endregion Test.Api.DashboardDetailRoutes
