# #region TestGitStatusRoute [TYPE Module] [C:3] [SEMANTICS tests, git, api, status, no_repo]
# @BRIEF Validate status endpoint behavior for missing and error repository states.
# @LAYER Domain
# @RELATION BINDS_TO -> [EXT:frontend:GitApi]
import asyncio
import pytest
from unittest.mock import MagicMock

from fastapi import HTTPException

from src.api.routes import git as git_routes


# #region test_get_repository_status_returns_no_repo_payload_for_missing_repo [TYPE Function]
# @RELATION BINDS_TO -> TestGitStatusRoute
# @BRIEF Ensure missing local repository is represented as NO_REPO payload instead of an API error.
# @PRE  GitService.get_status raises HTTPException(404).
# @POST  Route returns a deterministic NO_REPO status payload.
def test_get_repository_status_returns_no_repo_payload_for_missing_repo(monkeypatch):
    class MissingRepoGitService:
        def _get_repo_path(self, dashboard_id: int) -> str:
            return f"/tmp/missing-repo-{dashboard_id}"
        def get_status(self, dashboard_id: int) -> dict:
            raise AssertionError("get_status must not be called when repository path is missing")
    monkeypatch.setattr(git_routes, "git_service", MissingRepoGitService())
    response = asyncio.run(git_routes.get_repository_status(34))
    assert response["sync_status"] == "NO_REPO"
    assert response["sync_state"] == "NO_REPO"
    assert response["has_repo"] is False
    assert response["current_branch"] is None
# #endregion test_get_repository_status_returns_no_repo_payload_for_missing_repo
# #region test_get_repository_status_propagates_non_404_http_exception [TYPE Function]
# @RELATION BINDS_TO -> TestGitStatusRoute
# @BRIEF Ensure HTTP exceptions other than 404 are not masked.
# @PRE  GitService.get_status raises HTTPException with non-404 status.
# @POST  Raised exception preserves original status and detail.
def test_get_repository_status_propagates_non_404_http_exception(monkeypatch):
    class ConflictGitService:
        def _get_repo_path(self, dashboard_id: int) -> str:
            return f"/tmp/existing-repo-{dashboard_id}"
        def get_status(self, dashboard_id: int) -> dict:
            raise HTTPException(status_code=409, detail="Conflict")
    monkeypatch.setattr(git_routes, "git_service", ConflictGitService())
    monkeypatch.setattr(git_routes.os.path, "exists", lambda _path: True)
    with pytest.raises(HTTPException) as exc_info:
        asyncio.run(git_routes.get_repository_status(34))
    assert exc_info.value.status_code == 409
    assert exc_info.value.detail == "Conflict"
# #endregion test_get_repository_status_propagates_non_404_http_exception
# #region test_get_repository_diff_propagates_http_exception [TYPE Function]
# @RELATION BINDS_TO -> TestGitStatusRoute
# @BRIEF Ensure diff endpoint preserves domain HTTP errors from GitService.
# @PRE  GitService.get_diff raises HTTPException.
# @POST  Endpoint raises same HTTPException values.
def test_get_repository_diff_propagates_http_exception(monkeypatch):
    class DiffGitService:
        def get_diff(self, dashboard_id: int, file_path=None, staged: bool = False) -> str:
            raise HTTPException(status_code=404, detail="Repository missing")
    monkeypatch.setattr(git_routes, "git_service", DiffGitService())
    with pytest.raises(HTTPException) as exc_info:
        asyncio.run(git_routes.get_repository_diff(12))
    assert exc_info.value.status_code == 404
    assert exc_info.value.detail == "Repository missing"
# #endregion test_get_repository_diff_propagates_http_exception
# #region test_get_history_wraps_unexpected_error_as_500 [TYPE Function]
# @RELATION BINDS_TO -> TestGitStatusRoute
# @BRIEF Ensure non-HTTP exceptions in history endpoint become deterministic 500 errors.
# @PRE  GitService.get_commit_history raises ValueError.
# @POST  Endpoint returns HTTPException with status 500 and route context.
def test_get_history_wraps_unexpected_error_as_500(monkeypatch):
    class HistoryGitService:
        def get_commit_history(self, dashboard_id: int, limit: int = 50):
            raise ValueError("broken parser")
    monkeypatch.setattr(git_routes, "git_service", HistoryGitService())
    with pytest.raises(HTTPException) as exc_info:
        asyncio.run(git_routes.get_history(12))
    assert exc_info.value.status_code == 500
    assert exc_info.value.detail == "get_history failed: broken parser"
# #endregion test_get_history_wraps_unexpected_error_as_500
# #region test_commit_changes_wraps_unexpected_error_as_500 [TYPE Function]
# @RELATION BINDS_TO -> TestGitStatusRoute
# @BRIEF Ensure commit endpoint does not leak unexpected errors as 400.
# @PRE  GitService.commit_changes raises RuntimeError.
# @POST  Endpoint raises HTTPException(500) with route context.
def test_commit_changes_wraps_unexpected_error_as_500(monkeypatch):
    class CommitGitService:
        def commit_changes(self, dashboard_id: int, message: str, files):
            raise RuntimeError("index lock")
    class CommitPayload:
        message = "test"
        files = ["dashboards/a.yaml"]
    monkeypatch.setattr(git_routes, "git_service", CommitGitService())
    with pytest.raises(HTTPException) as exc_info:
        asyncio.run(git_routes.commit_changes(12, CommitPayload()))
    assert exc_info.value.status_code == 500
    assert exc_info.value.detail == "commit_changes failed: index lock"
# #endregion test_commit_changes_wraps_unexpected_error_as_500
# #region test_get_repository_status_batch_returns_mixed_statuses [TYPE Function]
# @RELATION BINDS_TO -> TestGitStatusRoute
# @BRIEF Ensure batch endpoint returns per-dashboard statuses in one response.
# @PRE  Some repositories are missing and some are initialized.
# @POST  Returned map includes resolved status for each requested dashboard ID.
def test_get_repository_status_batch_returns_mixed_statuses(monkeypatch):
    class BatchGitService:
        def _get_repo_path(self, dashboard_id: int) -> str:
            return f"/tmp/repo-{dashboard_id}"
        def get_status(self, dashboard_id: int) -> dict:
            if dashboard_id == 2:
                return {"sync_state": "SYNCED", "sync_status": "OK"}
            raise HTTPException(status_code=404, detail="not found")
    monkeypatch.setattr(git_routes, "git_service", BatchGitService())
    monkeypatch.setattr(git_routes.os.path, "exists", lambda path: path.endswith("/repo-2"))
    class BatchRequest:
        dashboard_ids = [1, 2]
    response = asyncio.run(git_routes.get_repository_status_batch(BatchRequest()))
    assert response.statuses["1"]["sync_status"] == "NO_REPO"
    assert response.statuses["2"]["sync_state"] == "SYNCED"
# #endregion test_get_repository_status_batch_returns_mixed_statuses
# #region test_get_repository_status_batch_marks_item_as_error_on_service_failure [TYPE Function]
# @RELATION BINDS_TO -> TestGitStatusRoute
# @BRIEF Ensure batch endpoint marks failed items as ERROR without failing entire request.
# @PRE  GitService raises non-HTTP exception for one dashboard.
# @POST  Failed dashboard status is marked as ERROR.
def test_get_repository_status_batch_marks_item_as_error_on_service_failure(monkeypatch):
    class BatchErrorGitService:
        def _get_repo_path(self, dashboard_id: int) -> str:
            return f"/tmp/repo-{dashboard_id}"
        def get_status(self, dashboard_id: int) -> dict:
            raise RuntimeError("boom")
    monkeypatch.setattr(git_routes, "git_service", BatchErrorGitService())
    monkeypatch.setattr(git_routes.os.path, "exists", lambda _path: True)
    class BatchRequest:
        dashboard_ids = [9]
    response = asyncio.run(git_routes.get_repository_status_batch(BatchRequest()))
    assert response.statuses["9"]["sync_status"] == "ERROR"
    assert response.statuses["9"]["sync_state"] == "ERROR"
# #endregion test_get_repository_status_batch_marks_item_as_error_on_service_failure
# #region test_get_repository_status_batch_deduplicates_and_truncates_ids [TYPE Function]
# @RELATION BINDS_TO -> TestGitStatusRoute
# @BRIEF Ensure batch endpoint protects server from oversized payloads.
# @PRE  request includes duplicate IDs and more than MAX_REPOSITORY_STATUS_BATCH entries.
# @POST  Result contains unique IDs up to configured cap.
def test_get_repository_status_batch_deduplicates_and_truncates_ids(monkeypatch):
    class SafeBatchGitService:
        def _get_repo_path(self, dashboard_id: int) -> str:
            return f"/tmp/repo-{dashboard_id}"
        def get_status(self, dashboard_id: int) -> dict:
            return {"sync_state": "SYNCED", "sync_status": "OK"}
    monkeypatch.setattr(git_routes, "git_service", SafeBatchGitService())
    monkeypatch.setattr(git_routes.os.path, "exists", lambda _path: True)
    class BatchRequest:
        dashboard_ids = [1, 1] + list(range(2, 90))
    response = asyncio.run(git_routes.get_repository_status_batch(BatchRequest()))
    assert len(response.statuses) == git_routes.MAX_REPOSITORY_STATUS_BATCH
    assert "1" in response.statuses
# #endregion test_get_repository_status_batch_deduplicates_and_truncates_ids
# #region test_commit_changes_applies_profile_identity_before_commit [TYPE Function]
# @RELATION BINDS_TO -> TestGitStatusRoute
# @BRIEF Ensure commit route configures repository identity from profile preferences before commit call.
# @PRE  Profile preference contains git_username/git_email for current user.
# @POST  git_service.configure_identity receives resolved identity and commit proceeds.
def test_commit_changes_applies_profile_identity_before_commit(monkeypatch):
    class IdentityGitService:
        def __init__(self):
            self.configured_identity = None
            self.commit_payload = None
        def configure_identity(self, dashboard_id: int, git_username: str, git_email: str):
            self.configured_identity = (dashboard_id, git_username, git_email)
        def commit_changes(self, dashboard_id: int, message: str, files):
            self.commit_payload = (dashboard_id, message, files)
    class PreferenceRow:
        git_username = "user_1"
        git_email = "user1@mail.ru"
    class PreferenceQuery:
        def filter(self, *_args, **_kwargs):
            return self
        def first(self):
            return PreferenceRow()
    class DbStub:
        def query(self, _model):
            return PreferenceQuery()
    class UserStub:
        id = "u-1"
    class CommitPayload:
        message = "test"
        files = ["dashboards/a.yaml"]
    identity_service = IdentityGitService()
    monkeypatch.setattr(git_routes, "git_service", identity_service)
    monkeypatch.setattr(
        git_routes,
        "_resolve_dashboard_id_from_ref",
        lambda *_args, **_kwargs: 12,
    )
    asyncio.run(
        git_routes.commit_changes(
            "dashboard-12",
            CommitPayload(),
            config_manager=MagicMock(),
            db=DbStub(),
            current_user=UserStub(),
        )
    )
    assert identity_service.configured_identity == (12, "user_1", "user1@mail.ru")
    assert identity_service.commit_payload == (12, "test", ["dashboards/a.yaml"])
# #endregion test_commit_changes_applies_profile_identity_before_commit
# #region test_pull_changes_applies_profile_identity_before_pull [TYPE Function]
# @RELATION BINDS_TO -> TestGitStatusRoute
# @BRIEF Ensure pull route configures repository identity from profile preferences before pull call.
# @PRE  Profile preference contains git_username/git_email for current user.
# @POST  git_service.configure_identity receives resolved identity and pull proceeds.
def test_pull_changes_applies_profile_identity_before_pull(monkeypatch):
    class IdentityGitService:
        def __init__(self):
            self.configured_identity = None
            self.pulled_dashboard_id = None
        def configure_identity(self, dashboard_id: int, git_username: str, git_email: str):
            self.configured_identity = (dashboard_id, git_username, git_email)
        def pull_changes(self, dashboard_id: int):
            self.pulled_dashboard_id = dashboard_id
    class PreferenceRow:
        git_username = "user_1"
        git_email = "user1@mail.ru"
    class PreferenceQuery:
        def filter(self, *_args, **_kwargs):
            return self
        def first(self):
            return PreferenceRow()
    class DbStub:
        def query(self, _model):
            return PreferenceQuery()
    class UserStub:
        id = "u-1"
    identity_service = IdentityGitService()
    monkeypatch.setattr(git_routes, "git_service", identity_service)
    monkeypatch.setattr(
        git_routes,
        "_resolve_dashboard_id_from_ref",
        lambda *_args, **_kwargs: 12,
    )
    asyncio.run(
        git_routes.pull_changes(
            "dashboard-12",
            config_manager=MagicMock(),
            db=DbStub(),
            current_user=UserStub(),
        )
    )
    assert identity_service.configured_identity == (12, "user_1", "user1@mail.ru")
    assert identity_service.pulled_dashboard_id == 12
# #endregion test_pull_changes_applies_profile_identity_before_pull
# #region test_get_merge_status_returns_service_payload [TYPE Function]
# @RELATION BINDS_TO -> TestGitStatusRoute
# @BRIEF Ensure merge status route returns service payload as-is.
# @PRE  git_service.get_merge_status returns unfinished merge payload.
# @POST  Route response contains has_unfinished_merge=True.
def test_get_merge_status_returns_service_payload(monkeypatch):
    class MergeStatusGitService:
        def get_merge_status(self, dashboard_id: int) -> dict:
            return {
                "has_unfinished_merge": True,
                "repository_path": "/tmp/repo-12",
                "git_dir": "/tmp/repo-12/.git",
                "current_branch": "dev",
                "merge_head": "abc",
                "merge_message_preview": "merge msg",
                "conflicts_count": 2,
            }
    monkeypatch.setattr(git_routes, "git_service", MergeStatusGitService())
    monkeypatch.setattr(git_routes, "_resolve_dashboard_id_from_ref", lambda *_args, **_kwargs: 12)
    response = asyncio.run(
        git_routes.get_merge_status(
            "dashboard-12",
            config_manager=MagicMock(),
        )
    )
    assert response["has_unfinished_merge"] is True
    assert response["conflicts_count"] == 2
# #endregion test_get_merge_status_returns_service_payload
# #region test_resolve_merge_conflicts_passes_resolution_items_to_service [TYPE Function]
# @RELATION BINDS_TO -> TestGitStatusRoute
# @BRIEF Ensure merge resolve route forwards parsed resolutions to service.
# @PRE  resolve_data has one file strategy.
# @POST  Service receives normalized list and route returns resolved files.
def test_resolve_merge_conflicts_passes_resolution_items_to_service(monkeypatch):
    captured = {}
    class MergeResolveGitService:
        def resolve_merge_conflicts(self, dashboard_id: int, resolutions):
            captured["dashboard_id"] = dashboard_id
            captured["resolutions"] = resolutions
            return ["dashboards/a.yaml"]
    class ResolveData:
        class _Resolution:
            def dict(self):
                return {"file_path": "dashboards/a.yaml", "resolution": "mine", "content": None}
        resolutions = [_Resolution()]
    monkeypatch.setattr(git_routes, "git_service", MergeResolveGitService())
    monkeypatch.setattr(git_routes, "_resolve_dashboard_id_from_ref", lambda *_args, **_kwargs: 12)
    response = asyncio.run(
        git_routes.resolve_merge_conflicts(
            "dashboard-12",
            ResolveData(),
            config_manager=MagicMock(),
        )
    )
    assert captured["dashboard_id"] == 12
    assert captured["resolutions"][0]["resolution"] == "mine"
    assert response["resolved_files"] == ["dashboards/a.yaml"]
# #endregion test_resolve_merge_conflicts_passes_resolution_items_to_service
# #region test_abort_merge_calls_service_and_returns_result [TYPE Function]
# @RELATION BINDS_TO -> TestGitStatusRoute
# @BRIEF Ensure abort route delegates to service.
# @PRE  Service abort_merge returns aborted status.
# @POST  Route returns aborted status.
def test_abort_merge_calls_service_and_returns_result(monkeypatch):
    class AbortGitService:
        def abort_merge(self, dashboard_id: int):
            assert dashboard_id == 12
            return {"status": "aborted"}
    monkeypatch.setattr(git_routes, "git_service", AbortGitService())
    monkeypatch.setattr(git_routes, "_resolve_dashboard_id_from_ref", lambda *_args, **_kwargs: 12)
    response = asyncio.run(
        git_routes.abort_merge(
            "dashboard-12",
            config_manager=MagicMock(),
        )
    )
    assert response["status"] == "aborted"
# #endregion test_abort_merge_calls_service_and_returns_result
# #region test_continue_merge_passes_message_and_returns_commit [TYPE Function]
# @RELATION BINDS_TO -> TestGitStatusRoute
# @BRIEF Ensure continue route passes commit message to service.
# @PRE  continue_data.message is provided.
# @POST  Route returns committed status and hash.
def test_continue_merge_passes_message_and_returns_commit(monkeypatch):
    class ContinueGitService:
        def continue_merge(self, dashboard_id: int, message: str):
            assert dashboard_id == 12
            assert message == "Resolve all conflicts"
            return {"status": "committed", "commit_hash": "abc123"}
    class ContinueData:
        message = "Resolve all conflicts"
    monkeypatch.setattr(git_routes, "git_service", ContinueGitService())
    monkeypatch.setattr(git_routes, "_resolve_dashboard_id_from_ref", lambda *_args, **_kwargs: 12)
    response = asyncio.run(
        git_routes.continue_merge(
            "dashboard-12",
            ContinueData(),
            config_manager=MagicMock(),
        )
    )
    assert response["status"] == "committed"
    assert response["commit_hash"] == "abc123"
# #endregion test_continue_merge_passes_message_and_returns_commit
# #endregion TestGitStatusRoute
