# [DEF:backend.src.api.routes.__tests__.test_git_api:Module]
# @RELATION: VERIFIES -> src.api.routes.git
# @PURPOSE: API tests for Git configurations and repository operations.

import pytest
import asyncio
from unittest.mock import MagicMock
from fastapi import HTTPException
from src.api.routes import git as git_routes
from src.models.git import GitServerConfig, GitProvider, GitStatus, GitRepository

class DbMock:
    def __init__(self, data=None):
        self._data = data or []
        self._deleted = []
        self._added = []

    def query(self, model):
        self._model = model
        return self

    def filter(self, condition):
        # Simplistic mocking for tests, assuming equality checks
        for item in self._data:
            # We assume condition is an equality expression like GitServerConfig.id == "123"
            # It's hard to eval the condition exactly in a mock without complex parsing,
            # so we'll just return items where type matches.
            pass
        return self

    def first(self):
        for item in self._data:
            if hasattr(self, "_model") and isinstance(item, self._model):
                return item
        return None

    def all(self):
        return self._data

    def add(self, item):
        self._added.append(item)
        if not hasattr(item, "id") or not item.id:
            item.id = "mocked-id"
        self._data.append(item)

    def delete(self, item):
        self._deleted.append(item)
        if item in self._data:
            self._data.remove(item)

    def commit(self):
        pass

    def refresh(self, item):
        if not hasattr(item, "status"):
            item.status = GitStatus.CONNECTED
        if not hasattr(item, "last_validated"):
            item.last_validated = "2026-03-08T00:00:00Z"

def test_get_git_configs_masks_pat():
    """
    @PRE: Database session `db` is available.
    @POST: Returns a list of all GitServerConfig objects from the database with PAT masked.
    """
    db = DbMock([GitServerConfig(
        id="config-1", name="Test Server", provider=GitProvider.GITHUB,
        url="https://github.com", pat="secret-token",
        status=GitStatus.CONNECTED, last_validated="2026-03-08T00:00:00Z"
    )])
    
    result = asyncio.run(git_routes.get_git_configs(db=db))
    
    assert len(result) == 1
    assert result[0].pat == "********"
    assert result[0].name == "Test Server"

def test_create_git_config_persists_config():
    """
    @PRE: `config` contains valid GitServerConfigCreate data.
    @POST: A new GitServerConfig record is created in the database.
    """
    from src.api.routes.git_schemas import GitServerConfigCreate
    db = DbMock()
    config = GitServerConfigCreate(
        name="New Server", provider=GitProvider.GITLAB,
        url="https://gitlab.com", pat="new-token",
        default_branch="master"
    )
    
    result = asyncio.run(git_routes.create_git_config(config=config, db=db))
    
    assert len(db._added) == 1
    assert db._added[0].name == "New Server"
    assert db._added[0].pat == "new-token"
    assert result.name == "New Server"
    assert result.pat == "new-token" # Note: route returns unmasked until serialized by FastAPI usually, but in tests schema might catch it or not.

from src.api.routes.git_schemas import GitServerConfigUpdate

def test_update_git_config_modifies_record():
    """
    @PRE: `config_id` corresponds to an existing configuration.
    @POST: The configuration record is updated in the database, preserving PAT if masked is sent.
    """
    existing_config = GitServerConfig(
        id="config-1", name="Old Server", provider=GitProvider.GITHUB,
        url="https://github.com", pat="old-token",
        status=GitStatus.CONNECTED, last_validated="2026-03-08T00:00:00Z"
    )
    # The monkeypatched query will return existing_config as it's the only one in the list
    class SingleConfigDbMock:
        def query(self, *args): return self
        def filter(self, *args): return self
        def first(self): return existing_config
        def commit(self): pass
        def refresh(self, config): pass

    db = SingleConfigDbMock()
    update_data = GitServerConfigUpdate(name="Updated Server", pat="********")
    
    result = asyncio.run(git_routes.update_git_config(config_id="config-1", config_update=update_data, db=db))
    
    assert existing_config.name == "Updated Server"
    assert existing_config.pat == "old-token" # Ensure PAT is not overwritten with asterisks
    assert result.pat == "********"

def test_update_git_config_raises_404_if_not_found():
    """
    @PRE: `config_id` corresponds to a missing configuration.
    @THROW: HTTPException 404
    """
    db = DbMock([]) # Empty db
    update_data = GitServerConfigUpdate(name="Updated Server", pat="new-token")
    
    with pytest.raises(HTTPException) as exc_info:
        asyncio.run(git_routes.update_git_config(config_id="config-1", config_update=update_data, db=db))
    
    assert exc_info.value.status_code == 404
    assert exc_info.value.detail == "Configuration not found"

def test_delete_git_config_removes_record():
    """
    @PRE: `config_id` corresponds to an existing configuration.
    @POST: The configuration record is removed from the database.
    """
    existing_config = GitServerConfig(id="config-1")
    class SingleConfigDbMock:
        def query(self, *args): return self
        def filter(self, *args): return self
        def first(self): return existing_config
        def delete(self, config): self.deleted = config
        def commit(self): pass

    db = SingleConfigDbMock()
    
    result = asyncio.run(git_routes.delete_git_config(config_id="config-1", db=db))
    
    assert db.deleted == existing_config
    assert result["status"] == "success"

def test_test_git_config_validates_connection_successfully(monkeypatch):
    """
    @PRE: `config` contains provider, url, and pat.
    @POST: Returns success if the connection is validated via GitService.
    """
    class MockGitService:
        async def test_connection(self, provider, url, pat):
            return True

    monkeypatch.setattr(git_routes, "git_service", MockGitService())
    from src.api.routes.git_schemas import GitServerConfigCreate
    
    config = GitServerConfigCreate(
        name="Test Server", provider=GitProvider.GITHUB,
        url="https://github.com", pat="test-pat"
    )
    db = DbMock([])
    
    result = asyncio.run(git_routes.test_git_config(config=config, db=db))
    
    assert result["status"] == "success"

def test_test_git_config_fails_validation(monkeypatch):
    """
    @PRE: `config` contains provider, url, and pat BUT connection fails.
    @THROW: HTTPException 400
    """
    class MockGitService:
        async def test_connection(self, provider, url, pat):
            return False

    monkeypatch.setattr(git_routes, "git_service", MockGitService())
    from src.api.routes.git_schemas import GitServerConfigCreate
    
    config = GitServerConfigCreate(
        name="Test Server", provider=GitProvider.GITHUB,
        url="https://github.com", pat="bad-pat"
    )
    db = DbMock([])
    
    with pytest.raises(HTTPException) as exc_info:
        asyncio.run(git_routes.test_git_config(config=config, db=db))
    
    assert exc_info.value.status_code == 400
    assert exc_info.value.detail == "Connection failed"

def test_list_gitea_repositories_returns_payload(monkeypatch):
    """
    @PRE: config_id exists and provider is GITEA.
    @POST: Returns repositories visible to PAT user.
    """
    class MockGitService:
        async def list_gitea_repositories(self, url, pat):
            return [{"name": "test-repo", "full_name": "owner/test-repo", "private": True}]

    monkeypatch.setattr(git_routes, "git_service", MockGitService())
    existing_config = GitServerConfig(
        id="config-1", name="Gitea Server", provider=GitProvider.GITEA,
        url="https://gitea.local", pat="gitea-token"
    )
    db = DbMock([existing_config])
    
    result = asyncio.run(git_routes.list_gitea_repositories(config_id="config-1", db=db))
    
    assert len(result) == 1
    assert result[0].name == "test-repo"
    assert result[0].private is True

def test_list_gitea_repositories_rejects_non_gitea(monkeypatch):
    """
    @PRE: config_id exists and provider is NOT GITEA.
    @THROW: HTTPException 400
    """
    existing_config = GitServerConfig(
        id="config-1", name="GitHub Server", provider=GitProvider.GITHUB,
        url="https://github.com", pat="token"
    )
    db = DbMock([existing_config])
    
    with pytest.raises(HTTPException) as exc_info:
        asyncio.run(git_routes.list_gitea_repositories(config_id="config-1", db=db))
    
    assert exc_info.value.status_code == 400
    assert "GITEA provider only" in exc_info.value.detail

def test_create_remote_repository_creates_provider_repo(monkeypatch):
    """
    @PRE: config_id exists and PAT has creation permissions.
    @POST: Returns normalized remote repository payload.
    """
    class MockGitService:
        async def create_gitlab_repository(self, server_url, pat, name, private, description, auto_init, default_branch):
            return {
                "name": name,
                "full_name": f"user/{name}",
                "private": private,
                "clone_url": f"{server_url}/user/{name}.git"
            }

    monkeypatch.setattr(git_routes, "git_service", MockGitService())
    from src.api.routes.git_schemas import RemoteRepoCreateRequest
    
    existing_config = GitServerConfig(
        id="config-1", name="GitLab Server", provider=GitProvider.GITLAB,
        url="https://gitlab.com", pat="token"
    )
    db = DbMock([existing_config])
    
    request = RemoteRepoCreateRequest(name="new-repo", private=True, description="desc")
    result = asyncio.run(git_routes.create_remote_repository(config_id="config-1", request=request, db=db))
    
    assert result.provider == GitProvider.GITLAB
    assert result.name == "new-repo"
    assert result.full_name == "user/new-repo"

def test_init_repository_initializes_and_saves_binding(monkeypatch):
    """
    @PRE: `dashboard_ref` exists and `init_data` contains valid config_id and remote_url.
    @POST: Repository is initialized on disk and a GitRepository record is saved in DB.
    """
    from src.api.routes.git_schemas import RepoInitRequest
    
    class MockGitService:
        def init_repo(self, dashboard_id, remote_url, pat, repo_key, default_branch):
            self.init_called = True
        def _get_repo_path(self, dashboard_id, repo_key):
            return f"/tmp/repos/{repo_key}"

    git_service_mock = MockGitService()
    monkeypatch.setattr(git_routes, "git_service", git_service_mock)
    monkeypatch.setattr(git_routes, "_resolve_dashboard_id_from_ref", lambda *args, **kwargs: 123)
    monkeypatch.setattr(git_routes, "_resolve_repo_key_from_ref", lambda *args, **kwargs: "dashboard-123")
    
    existing_config = GitServerConfig(
        id="config-1", name="GitLab Server", provider=GitProvider.GITLAB,
        url="https://gitlab.com", pat="token", default_branch="main"
    )
    db = DbMock([existing_config])
    
    init_data = RepoInitRequest(config_id="config-1", remote_url="https://git.local/repo.git")
    
    result = asyncio.run(git_routes.init_repository(dashboard_ref="123", init_data=init_data, config_manager=MagicMock(), db=db))
    
    assert result["status"] == "success"
    assert git_service_mock.init_called is True
    assert len(db._added) == 1
    assert isinstance(db._added[0], GitRepository)
    assert db._added[0].dashboard_id == 123

# [/DEF:backend.src.api.routes.__tests__.test_git_api:Module]
