# #region Test.SupersetClient.DashboardsCrud.Edge3 [C:3] [TYPE Module] [SEMANTICS test,superset,dashboard,crud,edge,import,export,delete]
# @BRIEF Edge-case tests for SupersetDashboardsCrudMixin — export success flow, import retry logic, delete warning branch.
# @RELATION BINDS_TO -> [SupersetDashboardsCrudMixin]
# @TEST_EDGE: export_success -> full export flow with Content-Disposition header
# @TEST_EDGE: export_generated_filename -> filename generated when header not present
# @TEST_EDGE: export_non_zip -> non-zip Content-Type raises error
# @TEST_EDGE: import_file_name_none -> None file_name raises ValueError
# @TEST_EDGE: import_retry_after_delete -> delete_before_reimport retry path
# @TEST_EDGE: import_retry_none_target -> retry with unresolvable target raises
# @TEST_EDGE: import_retry_disabled -> reimport=False raises without retry
# @TEST_EDGE: import_retry_with_slug -> retry resolves target by slug
# @TEST_EDGE: delete_result_false -> response with result=False logs warning
# @TEST_EDGE: delete_int_id -> int ID calls correct endpoint
# @TEST_EDGE: delete_str_id -> str ID calls correct endpoint
from io import BytesIO
from pathlib import Path
from unittest.mock import AsyncMock, MagicMock
import zipfile

import httpx
import pytest

from src.core.config_models import Environment
from src.core.superset_client import SupersetClient
from src.core.utils.network import SupersetAPIError


def _make_client() -> SupersetClient:
    env = Environment(id="env1", name="T", url="http://test.local", username="u", password="p")
    client = SupersetClient(env)
    mc = MagicMock()
    mc.request = AsyncMock(return_value={})
    mc.fetch_paginated_data = AsyncMock(return_value=[])
    mc.upload_file = AsyncMock(return_value={"status": "ok"})
    client.client = mc
    client.network = mc
    return client


def _make_export_response():
    """Create a mock httpx.Response for export tests."""
    resp = MagicMock(spec=httpx.Response)
    resp.headers = {}
    resp.content = b""
    return resp


# ── export_dashboard: full success flow ───


class TestExportDashboardFullFlow:
    """Full export flow with valid response content."""

    @pytest.mark.asyncio
    async def test_export_success_with_filename(self):
        """export_dashboard returns (content, filename) from Content-Disposition header."""
        c = _make_client()
        resp = _make_export_response()
        resp.headers = {"Content-Type": "application/zip", "Content-Disposition": 'attachment; filename="dash_export.zip"'}
        resp.content = b"PK\x03\x04...zip_content..."
        c.client.request = AsyncMock(return_value=resp)
        content, filename = await c.export_dashboard(7)
        assert content == b"PK\x03\x04...zip_content..."
        assert filename is not None
        assert len(filename) > 0

    @pytest.mark.asyncio
    async def test_export_success_generated_filename(self):
        """export_dashboard generates filename when Content-Disposition header absent."""
        c = _make_client()
        resp = _make_export_response()
        resp.headers = {"Content-Type": "application/zip"}
        resp.content = b"real_zip_content"
        c.client.request = AsyncMock(return_value=resp)
        content, filename = await c.export_dashboard(7)
        assert content == b"real_zip_content"
        assert "7" in filename
        assert filename.endswith(".zip")

    @pytest.mark.asyncio
    async def test_export_non_zip_content_type(self):
        """Non-zip Content-Type raises SupersetAPIError."""
        c = _make_client()
        resp = _make_export_response()
        resp.headers = {"Content-Type": "text/plain"}
        resp.content = b"not_zip"
        c.client.request = AsyncMock(return_value=resp)
        with pytest.raises(SupersetAPIError, match="не ZIP"):
            await c.export_dashboard(7)


# ── import_dashboard: edge cases ───


class TestImportDashboardEdgeCases:
    """Edge cases for import_dashboard including retry logic."""

    @pytest.mark.asyncio
    async def test_file_name_none(self):
        """import_dashboard with None file_name raises ValueError."""
        c = _make_client()
        with pytest.raises(ValueError, match="file_name cannot be None"):
            await c.import_dashboard(None)

    @pytest.mark.asyncio
    async def test_import_retry_after_delete(self, tmp_path):
        """delete_before_reimport=True retries after delete."""
        c = _make_client()
        c.delete_before_reimport = True
        p = tmp_path / "import_retry.zip"
        buf = BytesIO()
        with zipfile.ZipFile(buf, "w") as zf:
            zf.writestr("dash/metadata.yaml", "title: Test")
        p.write_bytes(buf.getvalue())

        call_count = 0

        async def mock_do_import(file_name):
            nonlocal call_count
            call_count += 1
            if call_count == 1:
                raise Exception("Import failed on first attempt")
            return {"id": 123, "status": "imported"}

        c._do_import = AsyncMock(side_effect=mock_do_import)
        c.delete_dashboard = AsyncMock()
        c._resolve_target_id_for_delete = AsyncMock(return_value=99)

        result = await c.import_dashboard(str(p), dash_id=5)
        assert call_count == 2
        c.delete_dashboard.assert_awaited_once_with(99)
        assert result["id"] == 123

    @pytest.mark.asyncio
    async def test_import_retry_none_target(self, tmp_path):
        """delete_before_reimport=True but unresolvable target re-raises original error."""
        c = _make_client()
        c.delete_before_reimport = True
        p = tmp_path / "import_none_target.zip"
        buf = BytesIO()
        with zipfile.ZipFile(buf, "w") as zf:
            zf.writestr("dash/metadata.yaml", "title: Test")
        p.write_bytes(buf.getvalue())
        c._do_import = AsyncMock(side_effect=Exception("Import failed"))
        c._resolve_target_id_for_delete = AsyncMock(return_value=None)
        with pytest.raises(Exception, match="Import failed"):
            await c.import_dashboard(str(p))

    @pytest.mark.asyncio
    async def test_import_retry_disabled(self, tmp_path):
        """delete_before_reimport=False raises on first failure without retry."""
        c = _make_client()
        c.delete_before_reimport = False
        p = tmp_path / "import_no_retry.zip"
        buf = BytesIO()
        with zipfile.ZipFile(buf, "w") as zf:
            zf.writestr("dash/metadata.yaml", "title: Test")
        p.write_bytes(buf.getvalue())
        c._do_import = AsyncMock(side_effect=Exception("First attempt failed"))
        c.delete_dashboard = AsyncMock()
        with pytest.raises(Exception, match="First attempt failed"):
            await c.import_dashboard(str(p))
        c.delete_dashboard.assert_not_called()

    @pytest.mark.asyncio
    async def test_import_retry_with_slug(self, tmp_path):
        """retry resolves target by slug when dash_id is None."""
        c = _make_client()
        c.delete_before_reimport = True
        p = tmp_path / "import_slug.zip"
        buf = BytesIO()
        with zipfile.ZipFile(buf, "w") as zf:
            zf.writestr("dash/metadata.yaml", "title: Test")
        p.write_bytes(buf.getvalue())
        c._do_import = AsyncMock(side_effect=[Exception("fail"), {"id": 456}])
        c.delete_dashboard = AsyncMock()
        c._resolve_target_id_for_delete = AsyncMock(return_value=77)
        result = await c.import_dashboard(str(p), dash_slug="my-dashboard")
        c._resolve_target_id_for_delete.assert_awaited_once_with(None, "my-dashboard")
        c.delete_dashboard.assert_awaited_once_with(77)
        assert result["id"] == 456


# ── delete_dashboard: warning branch ───


class TestDeleteDashboardWarning:
    """Edge cases for delete_dashboard response handling."""

    @pytest.mark.asyncio
    async def test_result_false_logs_warning(self):
        """Response with result=False triggers warning branch."""
        c = _make_client()
        c.client.request = AsyncMock(return_value={"result": False})
        await c.delete_dashboard(42)

    @pytest.mark.asyncio
    async def test_result_true_is_success(self):
        """Response with result=True logs success."""
        c = _make_client()
        c.client.request = AsyncMock(return_value={"result": True})
        await c.delete_dashboard(42)

    @pytest.mark.asyncio
    async def test_delete_int_id(self):
        """delete_dashboard with int ID calls correct endpoint."""
        c = _make_client()
        c.client.request = AsyncMock(return_value={"result": True})
        await c.delete_dashboard(123)
        c.client.request.assert_awaited_once_with(method="DELETE", endpoint="/dashboard/123")

    @pytest.mark.asyncio
    async def test_delete_str_id(self):
        """delete_dashboard with str ID calls correct endpoint."""
        c = _make_client()
        c.client.request = AsyncMock(return_value={"result": True})
        await c.delete_dashboard("dash-abc")
        c.client.request.assert_awaited_once_with(method="DELETE", endpoint="/dashboard/dash-abc")
# #endregion Test.SupersetClient.DashboardsCrud.Edge3
