# app/modules/tenancy/tests/integration/test_store_team_roles_api.py
"""
Integration tests for store team role CRUD API endpoints.

Tests the role management endpoints at:
    /api/v1/store/team/roles

Authentication: Overrides get_current_store_from_cookie_or_header to return
a UserContext with the correct token_store_id. The test user is the merchant
owner, so all permission checks pass (owner bypass).
"""

import uuid

import pytest

from app.api.deps import get_current_store_from_cookie_or_header
from app.modules.tenancy.models import Merchant, Role, Store, StoreUser, User
from app.modules.tenancy.schemas.auth import UserContext
from main import app

# ============================================================================
# Fixtures
# ============================================================================

BASE = "/api/v1/store/team"


@pytest.fixture
def role_owner(db):
    """Create a store owner user for role tests."""
    from middleware.auth import AuthManager

    auth = AuthManager()
    uid = uuid.uuid4().hex[:8]
    user = User(
        email=f"roleowner_{uid}@test.com",
        username=f"roleowner_{uid}",
        hashed_password=auth.hash_password("rolepass123"),
        role="merchant_owner",
        is_active=True,
    )
    db.add(user)
    db.commit()
    db.refresh(user)
    return user


@pytest.fixture
def role_merchant(db, role_owner):
    """Create a merchant owned by role_owner."""
    merchant = Merchant(
        name="Role Test Merchant",
        owner_user_id=role_owner.id,
        contact_email=role_owner.email,
        is_active=True,
        is_verified=True,
    )
    db.add(merchant)
    db.commit()
    db.refresh(merchant)
    return merchant


@pytest.fixture
def role_store(db, role_merchant):
    """Create a store for role tests."""
    uid = uuid.uuid4().hex[:8]
    store = Store(
        merchant_id=role_merchant.id,
        store_code=f"ROLETEST_{uid.upper()}",
        subdomain=f"roletest{uid}",
        name=f"Role Test Store {uid}",
        is_active=True,
        is_verified=True,
    )
    db.add(store)
    db.commit()
    db.refresh(store)
    return store


@pytest.fixture
def role_store_user(db, role_store, role_owner):
    """Create a StoreUser association for the owner."""
    store_user = StoreUser(
        store_id=role_store.id,
        user_id=role_owner.id,
        is_active=True,
    )
    db.add(store_user)
    db.commit()
    db.refresh(store_user)
    return store_user


@pytest.fixture
def role_auth(role_owner, role_store, role_store_user):
    """Override auth dependency to simulate authenticated store owner.

    Overrides get_current_store_from_cookie_or_header so that both
    require_store_owner and require_store_permission(...) inner functions
    receive the correct UserContext. The owner bypass ensures all
    permission checks pass.
    """
    user_context = UserContext(
        id=role_owner.id,
        email=role_owner.email,
        username=role_owner.username,
        role="merchant_owner",
        is_active=True,
        token_store_id=role_store.id,
    )

    def _override():
        return user_context

    app.dependency_overrides[get_current_store_from_cookie_or_header] = _override
    yield {"Authorization": "Bearer fake-token"}
    app.dependency_overrides.pop(get_current_store_from_cookie_or_header, None)


@pytest.fixture
def existing_custom_role(db, role_store):
    """Create an existing custom role for update/delete tests."""
    role = Role(
        store_id=role_store.id,
        name="test_custom_role",
        permissions=["products.view", "orders.view"],
    )
    db.add(role)
    db.commit()
    db.refresh(role)
    return role


# ============================================================================
# GET /team/roles
# ============================================================================


@pytest.mark.integration
@pytest.mark.tenancy
class TestListRoles:
    """Tests for GET /api/v1/store/team/roles."""

    def test_list_roles_creates_defaults(self, client, role_auth, role_store):
        """GET /roles returns default preset roles when none exist."""
        response = client.get(f"{BASE}/roles", headers=role_auth)
        assert response.status_code == 200
        data = response.json()
        assert "roles" in data
        assert data["total"] >= 5
        role_names = {r["name"] for r in data["roles"]}
        assert "manager" in role_names
        assert "staff" in role_names
        assert "support" in role_names
        assert "viewer" in role_names
        assert "marketing" in role_names

    def test_list_roles_includes_custom(self, client, role_auth, existing_custom_role):
        """GET /roles includes custom roles alongside presets."""
        response = client.get(f"{BASE}/roles", headers=role_auth)
        assert response.status_code == 200
        data = response.json()
        role_names = {r["name"] for r in data["roles"]}
        assert "test_custom_role" in role_names

    def test_list_roles_response_shape(self, client, role_auth, existing_custom_role):
        """Each role in the response has expected fields."""
        response = client.get(f"{BASE}/roles", headers=role_auth)
        assert response.status_code == 200
        role = response.json()["roles"][0]
        assert "id" in role
        assert "name" in role
        assert "permissions" in role
        assert isinstance(role["permissions"], list)


# ============================================================================
# POST /team/roles
# ============================================================================


@pytest.mark.integration
@pytest.mark.tenancy
class TestCreateRole:
    """Tests for POST /api/v1/store/team/roles."""

    def test_create_custom_role_success(self, client, role_auth, role_store):
        """POST /roles creates a new custom role."""
        response = client.post(
            f"{BASE}/roles",
            headers=role_auth,
            json={
                "name": "api_test_role",
                "permissions": ["products.view", "orders.view"],
            },
        )
        assert response.status_code == 201
        data = response.json()
        assert data["name"] == "api_test_role"
        assert "products.view" in data["permissions"]
        assert "orders.view" in data["permissions"]

    def test_create_role_preset_name_rejected(self, client, role_auth):
        """POST /roles rejects preset role names."""
        response = client.post(
            f"{BASE}/roles",
            headers=role_auth,
            json={
                "name": "manager",
                "permissions": ["products.view"],
            },
        )
        assert response.status_code == 422
        assert "preset" in response.json()["message"].lower()

    def test_create_role_duplicate_name_rejected(
        self, client, role_auth, existing_custom_role
    ):
        """POST /roles rejects duplicate role names."""
        response = client.post(
            f"{BASE}/roles",
            headers=role_auth,
            json={
                "name": "test_custom_role",
                "permissions": ["products.view"],
            },
        )
        assert response.status_code == 422
        assert "already exists" in response.json()["message"].lower()

    def test_create_role_invalid_permissions(self, client, role_auth):
        """POST /roles rejects invalid permission IDs."""
        response = client.post(
            f"{BASE}/roles",
            headers=role_auth,
            json={
                "name": "bad_perms_role",
                "permissions": ["totally.fake.permission"],
            },
        )
        assert response.status_code == 422
        assert "invalid" in response.json()["message"].lower()


# ============================================================================
# PUT /team/roles/{role_id}
# ============================================================================


@pytest.mark.integration
@pytest.mark.tenancy
class TestUpdateRole:
    """Tests for PUT /api/v1/store/team/roles/{role_id}."""

    def test_update_role_name(self, client, role_auth, existing_custom_role):
        """PUT /roles/{id} updates the role name."""
        response = client.put(
            f"{BASE}/roles/{existing_custom_role.id}",
            headers=role_auth,
            json={
                "name": "renamed_role",
                "permissions": existing_custom_role.permissions,
            },
        )
        assert response.status_code == 200
        assert response.json()["name"] == "renamed_role"

    def test_update_role_permissions(self, client, role_auth, existing_custom_role):
        """PUT /roles/{id} updates permissions."""
        response = client.put(
            f"{BASE}/roles/{existing_custom_role.id}",
            headers=role_auth,
            json={
                "name": existing_custom_role.name,
                "permissions": ["products.view", "products.edit", "orders.view", "orders.edit"],
            },
        )
        assert response.status_code == 200
        data = response.json()
        assert "products.edit" in data["permissions"]
        assert "orders.edit" in data["permissions"]

    def test_update_nonexistent_role(self, client, role_auth):
        """PUT /roles/{id} returns 400 for non-existent role."""
        response = client.put(
            f"{BASE}/roles/99999",
            headers=role_auth,
            json={"name": "whatever", "permissions": []},
        )
        assert response.status_code == 422

    def test_update_role_rename_to_preset_rejected(
        self, client, role_auth, existing_custom_role
    ):
        """PUT /roles/{id} rejects renaming to a preset name."""
        response = client.put(
            f"{BASE}/roles/{existing_custom_role.id}",
            headers=role_auth,
            json={"name": "staff", "permissions": existing_custom_role.permissions},
        )
        assert response.status_code == 422
        assert "preset" in response.json()["message"].lower()


# ============================================================================
# DELETE /team/roles/{role_id}
# ============================================================================


@pytest.mark.integration
@pytest.mark.tenancy
class TestDeleteRole:
    """Tests for DELETE /api/v1/store/team/roles/{role_id}."""

    def test_delete_custom_role_success(self, client, role_auth, existing_custom_role, db):
        """DELETE /roles/{id} removes a custom role."""
        response = client.delete(
            f"{BASE}/roles/{existing_custom_role.id}",
            headers=role_auth,
        )
        assert response.status_code == 204

        # Verify role is deleted
        db.expire_all()
        deleted = db.query(Role).filter(Role.id == existing_custom_role.id).first()
        assert deleted is None

    def test_delete_nonexistent_role(self, client, role_auth):
        """DELETE /roles/{id} returns 400 for non-existent role."""
        response = client.delete(
            f"{BASE}/roles/99999",
            headers=role_auth,
        )
        assert response.status_code == 422

    def test_delete_preset_role_rejected(self, client, role_auth, role_store, db):
        """DELETE /roles/{id} rejects deleting preset roles."""
        preset_role = Role(
            store_id=role_store.id,
            name="staff",
            permissions=["orders.view"],
        )
        db.add(preset_role)
        db.commit()
        db.refresh(preset_role)

        response = client.delete(
            f"{BASE}/roles/{preset_role.id}",
            headers=role_auth,
        )
        assert response.status_code == 422
        assert "preset" in response.json()["message"].lower()


# ============================================================================
# GET /team/permissions/catalog
# ============================================================================


@pytest.mark.integration
@pytest.mark.tenancy
class TestPermissionCatalog:
    """Tests for GET /api/v1/store/team/permissions/catalog."""

    def test_catalog_returns_categories(self, client, role_auth):
        """GET /permissions/catalog returns categories with permissions."""
        response = client.get(f"{BASE}/permissions/catalog", headers=role_auth)
        assert response.status_code == 200
        data = response.json()
        assert "categories" in data
        assert len(data["categories"]) > 0

    def test_catalog_category_has_permissions(self, client, role_auth):
        """Each category contains permission items."""
        response = client.get(f"{BASE}/permissions/catalog", headers=role_auth)
        data = response.json()
        for category in data["categories"]:
            assert "id" in category
            assert "label" in category
            assert "permissions" in category
            assert len(category["permissions"]) > 0

    def test_catalog_permission_has_metadata(self, client, role_auth):
        """Each permission has id, label, description, and is_owner_only."""
        response = client.get(f"{BASE}/permissions/catalog", headers=role_auth)
        data = response.json()
        perm = data["categories"][0]["permissions"][0]
        assert "id" in perm
        assert "label" in perm
        assert "description" in perm
        assert "is_owner_only" in perm

    def test_catalog_includes_team_permissions(self, client, role_auth):
        """Catalog includes team permissions from tenancy module."""
        response = client.get(f"{BASE}/permissions/catalog", headers=role_auth)
        data = response.json()
        all_perm_ids = {
            p["id"]
            for cat in data["categories"]
            for p in cat["permissions"]
        }
        assert "team.view" in all_perm_ids
        assert "team.edit" in all_perm_ids
