# #region TestAPIKeyRoutes [C:3] [TYPE Module] [SEMANTICS test, api_key, routes, crud]
# @BRIEF Contract tests for admin API key CRUD endpoints — list, create (raw key present), revoke, idempotent revoke.
# @RELATION BINDS_TO -> [AdminApiKeyRoutes]
# @TEST_CONTRACT: GET /api/admin/api-keys -> list of keys without raw_key or key_hash
# @TEST_CONTRACT: POST /api/admin/api-keys -> 201 with raw_key in response
# @TEST_CONTRACT: DELETE /api/admin/api-keys/{id} -> 200 with status=revoked
# @TEST_EDGE: revoked_key_delete -> 404
# @TEST_EDGE: missing_name -> 422
# @TEST_EDGE: missing_permissions -> 422
import pytest
import sys
from unittest.mock import MagicMock

from fastapi.testclient import TestClient

# Patch GitService at module level to prevent /app/storage/repositories error
_mock_git_svc_cls = MagicMock()
_mock_git_svc_cls.return_value = MagicMock()
sys.modules['src.services.git._base'] = MagicMock(GitService=_mock_git_svc_cls)
sys.modules['src.services.git_service'] = MagicMock(GitService=_mock_git_svc_cls)


# ── Fixtures ──────────────────────────────────────────────────

@pytest.fixture
def client():
    """Create a TestClient with all dependencies mocked."""
    from src.app import app
    return TestClient(app)


def _mock_admin_auth():
    """Apply dependency overrides for admin authentication."""
    from src.app import app
    from src.dependencies import get_current_user

    mock_role = MagicMock()
    mock_role.name = "Admin"
    mock_role.permissions = []

    mock_user = MagicMock()
    mock_user.username = "test-admin"
    mock_user.roles = [mock_role]

    async def _mock_get_current_user():
        return mock_user

    app.dependency_overrides[get_current_user] = _mock_get_current_user


def _clear_overrides():
    from src.app import app
    app.dependency_overrides = {}


@pytest.fixture(autouse=True)
def auth_mock():
    _mock_admin_auth()
    yield
    _clear_overrides()


@pytest.fixture
def mock_db():
    """Patch get_db dependency with in-memory SQLite session."""
    from sqlalchemy import create_engine
    from sqlalchemy.orm import sessionmaker
    from sqlalchemy.pool import StaticPool

    from src.app import app
    from src.dependencies import get_db
    from src.models.mapping import Base

    engine = create_engine(
        "sqlite:///:memory:",
        poolclass=StaticPool,
        connect_args={"check_same_thread": False},
    )
    Base.metadata.create_all(engine)
    Session = sessionmaker(bind=engine)
    session = Session()

    def _get_db_override():
        db = Session()
        try:
            yield db
        finally:
            db.close()

    app.dependency_overrides[get_db] = _get_db_override
    yield session
    app.dependency_overrides.pop(get_db, None)
    session.close()


# ── Tests ─────────────────────────────────────────────────────

class TestListApiKeys:
    """Contract tests for GET /api/admin/api-keys."""

    # #region test_list_empty [C:2] [TYPE Function]
    # @BRIEF No keys → returns empty list.
    def test_list_empty(self, client, mock_db):
        response = client.get("/api/admin/api-keys/")
        assert response.status_code == 200
        data = response.json()
        assert isinstance(data, list)
        assert len(data) == 0
    # #endregion test_list_empty

    # #region test_list_with_keys [C:2] [TYPE Function]
    # @BRIEF Keys exist → returns list without raw_key or key_hash fields.
    def test_list_with_keys(self, client, mock_db):
        from src.core.auth.api_key import generate_api_key
        from src.models.api_key import APIKey

        # Create a key
        raw, prefix, key_hash = generate_api_key()
        api_key = APIKey(
            key_hash=key_hash,
            prefix=prefix,
            name="Test Key",
            permissions=["maintenance:start", "maintenance:end"],
            active=True,
        )
        mock_db.add(api_key)
        mock_db.commit()

        response = client.get("/api/admin/api-keys/")
        assert response.status_code == 200
        data = response.json()
        assert len(data) == 1
        assert data[0]["name"] == "Test Key"
        assert data[0]["prefix"] == prefix
        assert "raw_key" not in data[0]
        assert "key_hash" not in data[0]
    # #endregion test_list_with_keys


class TestCreateApiKey:
    """Contract tests for POST /api/admin/api-keys."""

    # #region test_create_valid [C:2] [TYPE Function]
    # @BRIEF Valid request → 201 with raw_key, prefix, id.
    def test_create_valid(self, client, mock_db):
        response = client.post(
            "/api/admin/api-keys/",
            json={
                "name": "CI/CD Pipeline",
                "permissions": ["maintenance:start", "maintenance:end"],
                "environment_id": "prod-env-1",
            },
        )
        assert response.status_code == 201
        data = response.json()
        assert "id" in data
        assert "raw_key" in data
        assert data["raw_key"].startswith("ssk_")
        assert data["prefix"] == data["raw_key"][:11]
        assert data["name"] == "CI/CD Pipeline"
        assert data["permissions"] == ["maintenance:start", "maintenance:end"]
        assert data["active"] is True
        assert data["environment_id"] == "prod-env-1"
        # Verify raw key is NOT in DB
        from src.models.api_key import APIKey
        db_key = mock_db.query(APIKey).filter(APIKey.id == data["id"]).first()
        assert db_key is not None
        assert db_key.key_hash != data["raw_key"]
        assert db_key.prefix == data["prefix"]
    # #endregion test_create_valid

    # #region test_create_missing_name [C:2] [TYPE Function]
    # @BRIEF Missing name → 400 or 422.
    def test_create_missing_name(self, client, mock_db):
        response = client.post(
            "/api/admin/api-keys/",
            json={
                "permissions": ["maintenance:start"],
            },
        )
        assert response.status_code in (400, 422)
    # #endregion test_create_missing_name

    # #region test_create_empty_permissions [C:2] [TYPE Function]
    # @BRIEF Empty permissions → 400 or 422.
    def test_create_empty_permissions(self, client, mock_db):
        response = client.post(
            "/api/admin/api-keys/",
            json={
                "name": "No Perms",
                "permissions": [],
            },
        )
        assert response.status_code in (400, 422)
    # #endregion test_create_empty_permissions


class TestRevokeApiKey:
    """Contract tests for DELETE /api/admin/api-keys/{id}."""

    # #region test_revoke_valid [C:2] [TYPE Function]
    # @BRIEF Revoke existing active key → 200 with status=revoked.
    def test_revoke_valid(self, client, mock_db):
        from src.core.auth.api_key import generate_api_key
        from src.models.api_key import APIKey

        raw, prefix, key_hash = generate_api_key()
        api_key = APIKey(
            key_hash=key_hash,
            prefix=prefix,
            name="To Revoke",
            permissions=["maintenance:start"],
            active=True,
        )
        mock_db.add(api_key)
        mock_db.commit()

        response = client.delete(f"/api/admin/api-keys/{api_key.id}")
        assert response.status_code == 200
        data = response.json()
        assert data["id"] == api_key.id
        assert data["status"] == "revoked"

        # Verify DB
        mock_db.refresh(api_key)
        assert api_key.active is False
    # #endregion test_revoke_valid

    # #region test_revoke_already_revoked [C:2] [TYPE Function]
    # @BRIEF Revoke already revoked key → 404.
    def test_revoke_already_revoked(self, client, mock_db):
        from src.core.auth.api_key import generate_api_key
        from src.models.api_key import APIKey

        raw, prefix, key_hash = generate_api_key()
        api_key = APIKey(
            key_hash=key_hash,
            prefix=prefix,
            name="Already Revoked",
            permissions=["maintenance:start"],
            active=False,  # already revoked
        )
        mock_db.add(api_key)
        mock_db.commit()

        response = client.delete(f"/api/admin/api-keys/{api_key.id}")
        assert response.status_code == 404
    # #endregion test_revoke_already_revoked

    # #region test_revoke_not_found [C:2] [TYPE Function]
    # @BRIEF Revoke non-existent key → 404.
    def test_revoke_not_found(self, client, mock_db):
        response = client.delete("/api/admin/api-keys/nonexistent-id")
        assert response.status_code == 404
    # #endregion test_revoke_not_found
# #endregion TestAPIKeyRoutes
