# #region Test.Api.GitHelpers [C:3] [TYPE Module] [SEMANTICS test,git,helpers]
# @BRIEF Unit tests for git helper functions.
# @RELATION BINDS_TO -> [GitHelpers]
# @TEST_EDGE: no_repo_path
# @TEST_EDGE: config_not_found -> 404
# @TEST_EDGE: slug_not_found -> 404
# @TEST_EDGE: empty_ref -> 400

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

import pytest

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


# ── _build_no_repo_status_payload ──

class TestBuildNoRepoStatusPayload:
    def test_payload_structure(self):
        from src.api.routes.git._helpers import _build_no_repo_status_payload
        payload = _build_no_repo_status_payload()
        assert payload["sync_status"] == "NO_REPO"
        assert payload["has_repo"] is False
        assert payload["is_dirty"] is False
        assert payload["untracked_files"] == []


# ── _handle_unexpected_git_route_error ──

class TestHandleUnexpectedGitRouteError:
    def test_raises_http_500(self):
        from fastapi import HTTPException
        from src.api.routes.git._helpers import _handle_unexpected_git_route_error
        with pytest.raises(HTTPException) as exc:
            _handle_unexpected_git_route_error("test_route", RuntimeError("boom"))
        assert exc.value.status_code == 500
        assert "test_route failed" in exc.value.detail


# ── _resolve_repository_status ──

class TestResolveRepositoryStatus:
    @pytest.mark.asyncio
    async def test_repo_path_exists(self):
        mock_service = MagicMock()
        mock_service._get_repo_path = AsyncMock(return_value="/tmp/some-repo")
        mock_service.get_status = AsyncMock(return_value={"sync_status": "OK"})

        with patch("src.api.routes.git._helpers.get_git_service", return_value=mock_service), \
             patch("os.path.exists", return_value=True):
            from src.api.routes.git._helpers import _resolve_repository_status
            result = await _resolve_repository_status(42)
        assert result["sync_status"] == "OK"

    @pytest.mark.asyncio
    async def test_repo_path_missing(self):
        mock_service = MagicMock()
        mock_service._get_repo_path = AsyncMock(return_value="/nonexistent")

        with patch("src.api.routes.git._helpers.get_git_service", return_value=mock_service), \
             patch("os.path.exists", return_value=False):
            from src.api.routes.git._helpers import _resolve_repository_status
            result = await _resolve_repository_status(42)
        assert result["sync_status"] == "NO_REPO"

    @pytest.mark.asyncio
    async def test_get_status_returns_404(self):
        from fastapi import HTTPException

        mock_service = MagicMock()
        mock_service._get_repo_path = AsyncMock(return_value="/tmp/repo")
        mock_service.get_status = AsyncMock(side_effect=HTTPException(status_code=404, detail="No repo"))

        with patch("src.api.routes.git._helpers.get_git_service", return_value=mock_service), \
             patch("os.path.exists", return_value=True):
            from src.api.routes.git._helpers import _resolve_repository_status
            result = await _resolve_repository_status(42)
        assert result["sync_status"] == "NO_REPO"

    @pytest.mark.asyncio
    async def test_get_status_raises_other_http(self):
        from fastapi import HTTPException

        mock_service = MagicMock()
        mock_service._get_repo_path = AsyncMock(return_value="/tmp/repo")
        mock_service.get_status = AsyncMock(side_effect=HTTPException(status_code=500, detail="Server error"))

        with patch("src.api.routes.git._helpers.get_git_service", return_value=mock_service), \
             patch("os.path.exists", return_value=True):
            from src.api.routes.git._helpers import _resolve_repository_status
            with pytest.raises(HTTPException) as exc:
                await _resolve_repository_status(42)
        assert exc.value.status_code == 500


# ── _get_git_config_or_404 ──

class TestGetGitConfigOr404:
    def test_config_found(self):
        mock_db = MagicMock()
        mock_db.query.return_value.filter.return_value.first.return_value = MagicMock(id="cfg-1")

        from src.api.routes.git._helpers import _get_git_config_or_404
        result = _get_git_config_or_404(mock_db, "cfg-1")
        assert result is not None

    def test_config_not_found(self):
        from fastapi import HTTPException
        mock_db = MagicMock()
        mock_db.query.return_value.filter.return_value.first.return_value = None

        from src.api.routes.git._helpers import _get_git_config_or_404
        with pytest.raises(HTTPException) as exc:
            _get_git_config_or_404(mock_db, "cfg-missing")
        assert exc.value.status_code == 404


# ── _find_dashboard_id_by_slug (sync) ──

class TestFindDashboardIdBySlug:
    @pytest.mark.asyncio
    async def test_found(self):
        client = AsyncMock()
        client.get_dashboards_page.return_value = (1, [{"id": 42}])

        from src.api.routes.git._helpers import _find_dashboard_id_by_slug
        result = await _find_dashboard_id_by_slug(client, "my-slug")
        assert result == 42

    @pytest.mark.asyncio
    async def test_not_found(self):
        client = AsyncMock()
        client.get_dashboards_page.return_value = (0, [])

        from src.api.routes.git._helpers import _find_dashboard_id_by_slug
        result = await _find_dashboard_id_by_slug(client, "ghost")
        assert result is None

    @pytest.mark.asyncio
    async def test_exception_then_second_query(self):
        client = AsyncMock()
        client.get_dashboards_page.side_effect = [
            Exception("Fail"),
            (1, [{"id": 99}]),
        ]

        from src.api.routes.git._helpers import _find_dashboard_id_by_slug
        result = await _find_dashboard_id_by_slug(client, "retry")
        assert result == 99


# ── _resolve_dashboard_id_from_ref (sync) ──

class TestResolveDashboardIdFromRef:
    @pytest.mark.asyncio
    async def test_numeric_ref(self):
        from src.api.routes.git._helpers import _resolve_dashboard_id_from_ref
        result = await _resolve_dashboard_id_from_ref("123", MagicMock())
        assert result == 123

    @pytest.mark.asyncio
    async def test_slug_ref(self):
        mock_config = MagicMock()
        env = MagicMock()
        env.id = "env-1"
        mock_config.get_environments.return_value = [env]

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

        with patch("src.api.routes.git._helpers.SupersetClient", return_value=client):
            from src.api.routes.git._helpers import _resolve_dashboard_id_from_ref
            result = await _resolve_dashboard_id_from_ref("my-slug", mock_config, "env-1")
        assert result == 42

    @pytest.mark.asyncio
    async def test_empty_ref(self):
        from fastapi import HTTPException
        from src.api.routes.git._helpers import _resolve_dashboard_id_from_ref
        with pytest.raises(HTTPException) as exc:
            await _resolve_dashboard_id_from_ref("", MagicMock())
        assert exc.value.status_code == 400

    @pytest.mark.asyncio
    async def test_slug_no_env_id(self):
        from fastapi import HTTPException
        from src.api.routes.git._helpers import _resolve_dashboard_id_from_ref
        with pytest.raises(HTTPException) as exc:
            await _resolve_dashboard_id_from_ref("slug", MagicMock())
        assert exc.value.status_code == 400
        assert "env_id is required" in exc.value.detail

    @pytest.mark.asyncio
    async def test_env_not_found(self):
        from fastapi import HTTPException
        mock_config = MagicMock()
        mock_config.get_environments.return_value = []

        from src.api.routes.git._helpers import _resolve_dashboard_id_from_ref
        with pytest.raises(HTTPException) as exc:
            await _resolve_dashboard_id_from_ref("slug", mock_config, "env-ghost")
        assert exc.value.status_code == 404

    @pytest.mark.asyncio
    async def test_slug_not_found(self):
        from fastapi import HTTPException
        mock_config = MagicMock()
        env = MagicMock()
        env.id = "env-1"
        mock_config.get_environments.return_value = [env]

        client = MagicMock()
        client.get_dashboards_page.return_value = (0, [])

        with patch("src.api.routes.git._helpers.SupersetClient", return_value=client):
            from src.api.routes.git._helpers import _resolve_dashboard_id_from_ref
            with pytest.raises(HTTPException) as exc:
                await _resolve_dashboard_id_from_ref("ghost-slug", mock_config, "env-1")
        assert exc.value.status_code == 404


# ── _find_dashboard_id_by_slug_async ──

class TestFindDashboardIdBySlugAsync:
    @pytest.mark.asyncio
    async def test_found(self):
        client = AsyncMock()
        client.get_dashboards_page.return_value = (1, [{"id": 42}])

        from src.api.routes.git._helpers import _find_dashboard_id_by_slug_async
        result = await _find_dashboard_id_by_slug_async(client, "slug")
        assert result == 42

    @pytest.mark.asyncio
    async def test_not_found(self):
        client = AsyncMock()
        client.get_dashboards_page.return_value = (0, [])

        from src.api.routes.git._helpers import _find_dashboard_id_by_slug_async
        result = await _find_dashboard_id_by_slug_async(client, "ghost")
        assert result is None


# ── _resolve_dashboard_id_from_ref_async ──

class TestResolveDashboardIdFromRefAsync:
    @pytest.mark.asyncio
    async def test_numeric_ref(self):
        from src.api.routes.git._helpers import _resolve_dashboard_id_from_ref_async
        result = await _resolve_dashboard_id_from_ref_async("123", MagicMock())
        assert result == 123

    @pytest.mark.asyncio
    async def test_slug_ref(self):
        mock_config = MagicMock()
        env = MagicMock()
        env.id = "env-1"
        mock_config.get_environments.return_value = [env]

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

        with patch("src.core.async_superset_client.AsyncSupersetClient", return_value=client):
            from src.api.routes.git._helpers import _resolve_dashboard_id_from_ref_async
            result = await _resolve_dashboard_id_from_ref_async("slug", mock_config, "env-1")
        assert result == 42
        client.aclose.assert_awaited_once()

    @pytest.mark.asyncio
    async def test_empty_ref(self):
        from fastapi import HTTPException
        from src.api.routes.git._helpers import _resolve_dashboard_id_from_ref_async
        with pytest.raises(HTTPException) as exc:
            await _resolve_dashboard_id_from_ref_async("", MagicMock())
        assert exc.value.status_code == 400

    @pytest.mark.asyncio
    async def test_slug_not_found_async(self):
        from fastapi import HTTPException
        mock_config = MagicMock()
        env = MagicMock()
        env.id = "env-1"
        mock_config.get_environments.return_value = [env]

        client = AsyncMock()
        client.get_dashboards_page.return_value = (0, [])
        client.aclose = AsyncMock()

        with patch("src.core.async_superset_client.AsyncSupersetClient", return_value=client):
            from src.api.routes.git._helpers import _resolve_dashboard_id_from_ref_async
            with pytest.raises(HTTPException) as exc:
                await _resolve_dashboard_id_from_ref_async("ghost", mock_config, "env-1")
        assert exc.value.status_code == 404
        client.aclose.assert_awaited_once()

    @pytest.mark.asyncio
    async def test_no_env_id(self):
        from fastapi import HTTPException
        from src.api.routes.git._helpers import _resolve_dashboard_id_from_ref_async
        with pytest.raises(HTTPException) as exc:
            await _resolve_dashboard_id_from_ref_async("slug", MagicMock())
        assert exc.value.status_code == 400


# ── _resolve_repo_key_from_ref ──

class TestResolveRepoKeyFromRef:
    @pytest.mark.asyncio
    async def test_slug_ref_returns_slug(self):
        from src.api.routes.git._helpers import _resolve_repo_key_from_ref
        result = await _resolve_repo_key_from_ref("my-dash", 42, MagicMock())
        assert result == "my-dash"

    @pytest.mark.asyncio
    async def test_numeric_ref_returns_dashboard_id_fallback(self):
        from src.api.routes.git._helpers import _resolve_repo_key_from_ref
        result = await _resolve_repo_key_from_ref("123", 42, MagicMock())
        assert result == "dashboard-42"

    @pytest.mark.asyncio
    async def test_numeric_ref_with_env_lookup(self):
        mock_config = MagicMock()
        env = MagicMock()
        env.id = "env-1"
        mock_config.get_environments.return_value = [env]

        client = AsyncMock()
        client.get_dashboard.return_value = {"result": {"slug": "real-slug"}}

        with patch("src.api.routes.git._helpers.SupersetClient", return_value=client):
            from src.api.routes.git._helpers import _resolve_repo_key_from_ref
            result = await _resolve_repo_key_from_ref("123", 42, mock_config, "env-1")
        assert result == "real-slug"

    @pytest.mark.asyncio
    async def test_env_lookup_fails_uses_fallback(self):
        mock_config = MagicMock()
        env = MagicMock()
        env.id = "env-1"
        mock_config.get_environments.return_value = [env]

        client = MagicMock()
        client.get_dashboard.side_effect = Exception("API error")

        with patch("src.api.routes.git._helpers.SupersetClient", return_value=client):
            from src.api.routes.git._helpers import _resolve_repo_key_from_ref
            result = await _resolve_repo_key_from_ref("123", 42, mock_config, "env-1")
        assert result == "dashboard-42"

    @pytest.mark.asyncio
    async def test_empty_ref_uses_fallback(self):
        from src.api.routes.git._helpers import _resolve_repo_key_from_ref
        result = await _resolve_repo_key_from_ref("", 42, MagicMock())
        assert result == "dashboard-42"


# ── _sanitize_optional_identity_value ──

class TestSanitizeOptionalIdentityValue:
    def test_none(self):
        from src.api.routes.git._helpers import _sanitize_optional_identity_value
        assert _sanitize_optional_identity_value(None) is None

    def test_empty(self):
        from src.api.routes.git._helpers import _sanitize_optional_identity_value
        assert _sanitize_optional_identity_value("") is None

    def test_whitespace(self):
        from src.api.routes.git._helpers import _sanitize_optional_identity_value
        assert _sanitize_optional_identity_value("  ") is None

    def test_valid(self):
        from src.api.routes.git._helpers import _sanitize_optional_identity_value
        assert _sanitize_optional_identity_value("  alice ") == "alice"


# ── _resolve_current_user_git_identity ──

class TestResolveCurrentUserGitIdentity:
    def test_db_is_none(self):
        from src.api.routes.git._helpers import _resolve_current_user_git_identity
        assert _resolve_current_user_git_identity(None, MagicMock()) is None

    def test_no_user_id(self):
        from src.api.routes.git._helpers import _resolve_current_user_git_identity
        user = MagicMock()
        user.id = None
        assert _resolve_current_user_git_identity(MagicMock(), user) is None

    def test_preference_found(self):
        mock_db = MagicMock()
        pref = MagicMock()
        pref.git_username = "alice"
        pref.git_email = "alice@example.com"
        mock_db.query.return_value.filter.return_value.first.return_value = pref

        user = MagicMock()
        user.id = "user-1"

        from src.api.routes.git._helpers import _resolve_current_user_git_identity
        result = _resolve_current_user_git_identity(mock_db, user)
        assert result == ("alice", "alice@example.com")

    def test_preference_not_found(self):
        mock_db = MagicMock()
        mock_db.query.return_value.filter.return_value.first.return_value = None

        user = MagicMock()
        user.id = "user-1"

        from src.api.routes.git._helpers import _resolve_current_user_git_identity
        assert _resolve_current_user_git_identity(mock_db, user) is None

    def test_preference_missing_git_fields(self):
        mock_db = MagicMock()
        pref = MagicMock()
        pref.git_username = None
        pref.git_email = None
        mock_db.query.return_value.filter.return_value.first.return_value = pref

        user = MagicMock()
        user.id = "user-1"

        from src.api.routes.git._helpers import _resolve_current_user_git_identity
        assert _resolve_current_user_git_identity(mock_db, user) is None

    def test_query_exception(self):
        mock_db = MagicMock()
        mock_db.query.return_value.filter.return_value.first.side_effect = Exception("DB error")

        user = MagicMock()
        user.id = "user-1"

        from src.api.routes.git._helpers import _resolve_current_user_git_identity
        assert _resolve_current_user_git_identity(mock_db, user) is None

    def test_db_without_query_attr(self):
        from src.api.routes.git._helpers import _resolve_current_user_git_identity
        assert _resolve_current_user_git_identity(object(), MagicMock()) is None


# ── _apply_git_identity_from_profile ──

class TestApplyGitIdentityFromProfile:
    @pytest.mark.asyncio
    async def test_identity_resolved_and_applied(self):
        mock_service = MagicMock()
        mock_service.configure_identity = AsyncMock()

        with patch("src.api.routes.git._helpers.get_git_service", return_value=mock_service), \
             patch("src.api.routes.git._helpers._resolve_current_user_git_identity",
                   return_value=("alice", "alice@example.com")):
            from src.api.routes.git._helpers import _apply_git_identity_from_profile
            await _apply_git_identity_from_profile(42, MagicMock(), MagicMock())
        mock_service.configure_identity.assert_awaited_once_with(42, "alice", "alice@example.com")

    @pytest.mark.asyncio
    async def test_identity_not_resolved(self):
        with patch("src.api.routes.git._helpers._resolve_current_user_git_identity",
                   return_value=None):
            from src.api.routes.git._helpers import _apply_git_identity_from_profile
            # Should not raise
            await _apply_git_identity_from_profile(42, MagicMock(), MagicMock())

    @pytest.mark.asyncio
    async def test_service_has_no_configure_identity(self):
        mock_service = MagicMock(spec=[])  # no configure_identity attribute

        with patch("src.api.routes.git._helpers.get_git_service", return_value=mock_service), \
             patch("src.api.routes.git._helpers._resolve_current_user_git_identity",
                   return_value=("alice", "alice@example.com")):
            from src.api.routes.git._helpers import _apply_git_identity_from_profile
            # Should not raise
            await _apply_git_identity_from_profile(42, MagicMock(), MagicMock())
# #endregion Test.Api.GitHelpers
