# #region Test.Git.Base.Coverage [C:3] [TYPE Module] [SEMANTICS test, git, base, coverage, repo, path, resolve]
# @BRIEF Additional edge coverage for GitServiceBase — _resolve_base_path relative root, _update_repo_local_path exception, _get_repo_path db-path-exists conditions, init_repo stale unlink exception.
# @RELATION BINDS_TO -> [GitServiceBase]

import contextlib
import sys
from pathlib import Path
sys.path.insert(0, str(Path(__file__).parent.parent.parent.parent / "src"))

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

import pytest
from git.exc import InvalidGitRepositoryError
from git import Repo

from src.services.git._base import GitServiceBase


class TestResolveBasePathCoverage:
    """Cover _resolve_base_path relative-root and combined-path branches."""

    def test_relative_root_resolved_against_project_root(self):
        """Root path is relative → resolved against project root."""
        with patch.object(GitServiceBase, '_ensure_base_path_exists'), \
             patch("src.services.git._base.SessionLocal") as mock_sl:
            mock_db = MagicMock()
            mock_sl.return_value = mock_db
            config = MagicMock()
            config.payload = {"settings": {"storage": {"root_path": "data", "repo_path": "repositories"}}}
            mock_db.query.return_value.filter.return_value.first.return_value = config
            svc = GitServiceBase(base_path="git_repos")
            assert svc.base_path is not None

    def test_db_config_with_repo_path(self):
        """repo_path is relative → combined with root."""
        with patch.object(GitServiceBase, '_ensure_base_path_exists'), \
             patch("src.services.git._base.SessionLocal") as mock_sl:
            mock_db = MagicMock()
            mock_sl.return_value = mock_db
            config = MagicMock()
            config.payload = {"settings": {"storage": {"root_path": "/data", "repo_path": "my-repos"}}}
            mock_db.query.return_value.filter.return_value.first.return_value = config
            svc = GitServiceBase(base_path="git_repos")
            assert svc.base_path is not None
            assert "/data/my-repos" in svc.base_path

    def test_db_config_absolute_repo_path(self):
        """repo_path is absolute → returned directly."""
        with patch.object(GitServiceBase, '_ensure_base_path_exists'), \
             patch("src.services.git._base.SessionLocal") as mock_sl:
            mock_db = MagicMock()
            mock_sl.return_value = mock_db
            config = MagicMock()
            config.payload = {"settings": {"storage": {"root_path": "/data", "repo_path": "/custom/path"}}}
            mock_db.query.return_value.filter.return_value.first.return_value = config
            svc = GitServiceBase(base_path="git_repos")
            assert "/custom/path" in svc.base_path

    def test_relative_root_relative_repo_combined(self):
        """Root path is relative, repo_path is relative → combined after resolving root."""
        with patch.object(GitServiceBase, '_ensure_base_path_exists'), \
             patch("src.services.git._base.SessionLocal") as mock_sl:
            mock_db = MagicMock()
            mock_sl.return_value = mock_db
            config = MagicMock()
            config.payload = {"settings": {"storage": {"root_path": "relative/data", "repo_path": "custom-repos"}}}
            mock_db.query.return_value.filter.return_value.first.return_value = config
            svc = GitServiceBase(base_path="git_repos")
            # root is relative → resolved against project_root (line 155)
            # repo_path is relative → combined with resolved root (line 159)
            assert "custom-repos" in svc.base_path

    def test_relative_root_absolute_repo(self):
        """Root path is relative, repo_path is absolute → absolute repo_path returned."""
        with patch.object(GitServiceBase, '_ensure_base_path_exists'), \
             patch("src.services.git._base.SessionLocal") as mock_sl:
            mock_db = MagicMock()
            mock_sl.return_value = mock_db
            config = MagicMock()
            config.payload = {"settings": {"storage": {"root_path": "relative/data", "repo_path": "/absolute/repo/path"}}}
            mock_db.query.return_value.filter.return_value.first.return_value = config
            svc = GitServiceBase(base_path="git_repos")
            # root is relative → resolved (line 155)
            # repo_path is absolute → returned directly (line 158)
            assert "/absolute/repo/path" in svc.base_path


class TestUpdateRepoLocalPathCoverage:
    """Cover exception handler in _update_repo_local_path."""

    def test_session_query_exception_caught(self):
        """SessionLocal creation fails → exception caught, no raise."""
        with patch.object(GitServiceBase, '_ensure_base_path_exists'), \
             patch.object(GitServiceBase, '_resolve_base_path', return_value="/tmp/test"):
            svc = GitServiceBase(base_path="/tmp/test")
            with patch("src.services.git._base.SessionLocal", side_effect=Exception("session error")):
                # Should not raise
                svc._update_repo_local_path(1, "/tmp/repo")

    def test_session_commit_exception_caught(self):
        """commit() fails → exception caught, no raise."""
        with patch.object(GitServiceBase, '_ensure_base_path_exists'), \
             patch.object(GitServiceBase, '_resolve_base_path', return_value="/tmp/test"):
            svc = GitServiceBase(base_path="/tmp/test")
            mock_session = MagicMock()
            mock_db_repo = MagicMock()
            mock_session.query.return_value.filter.return_value.first.return_value = mock_db_repo
            mock_session.commit.side_effect = Exception("commit error")
            with patch("src.services.git._base.SessionLocal", return_value=mock_session):
                svc._update_repo_local_path(1, "/tmp/repo")


class TestGetRepoPathCoverage:
    """Cover DB-path-exists-conditions in _get_repo_path."""

    @pytest.mark.asyncio
    async def test_db_path_exists_not_legacy(self):
        """DB local_path exists, not legacy → returned directly."""
        with patch.object(GitServiceBase, '_ensure_base_path_exists'):
            svc = GitServiceBase(base_path="git_repos")
            svc.base_path = "/tmp/new-base"
            svc.legacy_base_path = "/tmp/legacy"
            svc._uses_default_base_path = True

            with patch("src.services.git._base.SessionLocal") as mock_sl:
                mock_db = MagicMock()
                mock_sl.return_value = mock_db
                mock_db_repo = MagicMock()
                mock_db_repo.local_path = "/tmp/custom-path/repo"
                mock_db.query.return_value.filter.return_value.first.return_value = mock_db_repo
                with patch("os.path.exists", return_value=True), \
                     patch("os.path.abspath", side_effect=lambda x: x):
                    result = await svc._get_repo_path(1, "key")
                    assert result == "/tmp/custom-path/repo"

    @pytest.mark.asyncio
    async def test_db_path_exists_not_legacy_no_migration(self):
        """DB local_path exists, base_path differs from legacy but path outside legacy → returned directly."""
        with patch.object(GitServiceBase, '_ensure_base_path_exists'):
            svc = GitServiceBase(base_path="git_repos")
            svc.base_path = "/tmp/new-base"
            svc.legacy_base_path = "/tmp/legacy"
            svc._uses_default_base_path = True

            with patch("src.services.git._base.SessionLocal") as mock_sl:
                mock_db = MagicMock()
                mock_sl.return_value = mock_db
                mock_db_repo = MagicMock()
                mock_db_repo.local_path = "/tmp/independent/path"
                mock_db.query.return_value.filter.return_value.first.return_value = mock_db_repo
                with patch("os.path.exists", return_value=True), \
                     patch("os.path.abspath", side_effect=lambda x: x):
                    result = await svc._get_repo_path(1, "key")
                    assert result == "/tmp/independent/path"

    @pytest.mark.asyncio
    async def test_db_query_exception_caught(self):
        """DB query raises → warning logged, fallback path used."""
        with patch.object(GitServiceBase, '_ensure_base_path_exists'):
            svc = GitServiceBase(base_path="git_repos")
            svc.base_path = "/tmp/base"
            svc.legacy_base_path = "/tmp/legacy"
            svc._uses_default_base_path = True

            with patch("src.services.git._base.SessionLocal", side_effect=Exception("query failed")):
                result = await svc._get_repo_path(1, "my-key")
                assert "/tmp/base/my-key" in result


class TestInitRepoCoverage:
    """Cover stale_path.unlink exception in init_repo."""

    @pytest.mark.asyncio
    async def test_stale_unlink_exception(self):
        """shutil.rmtree succeeds, stale_path.unlink raises → pass, clone proceeds."""
        with patch.object(GitServiceBase, '_ensure_base_path_exists'), \
             patch.object(GitServiceBase, '_resolve_base_path', return_value="/tmp/test"):
            svc = GitServiceBase(base_path="/tmp/test")

            with patch.object(svc, "_get_repo_path", new_callable=AsyncMock, return_value="/tmp/repo"), \
                 patch("os.path.exists", return_value=True), \
                 patch("src.services.git._base.run_blocking") as mock_blocking, \
                 patch.object(svc, "_clone_with_auth", new_callable=AsyncMock) as mock_clone, \
                 patch.object(svc, "_ensure_gitflow_branches", create=True), \
                 patch.object(svc, '_locked', return_value=contextlib.nullcontext()), \
                 patch("pathlib.Path.exists", return_value=True):

                # mkdir OK, Repo raises InvalidGitRepositoryError, rmtree succeeds, unlink raises
                mock_blocking.side_effect = [
                    None,  # mkdir
                    InvalidGitRepositoryError("invalid"),  # Repo raises
                    None,  # shutil.rmtree succeeds (ignore_errors=True)
                    Exception("unlink fail"),  # stale_path.unlink raises → caught
                    MagicMock(),  # Repo from clone
                ]
                mock_clone.return_value = MagicMock()
                result = await svc.init_repo(1, "https://remote.com/repo.git", "pat")
                assert result is not None
# #endregion Test.Git.Base.Coverage
