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

Tests the member management endpoints at:
    /api/v1/store/team/members
    /api/v1/store/team/invite

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 member_owner(db):
    """Create a store owner user for member tests."""
    from middleware.auth import AuthManager

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


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


@pytest.fixture
def member_store(db, member_merchant):
    """Create a store for member tests."""
    uid = uuid.uuid4().hex[:8]
    store = Store(
        merchant_id=member_merchant.id,
        store_code=f"MEMTEST_{uid.upper()}",
        subdomain=f"memtest{uid}",
        name=f"Member Test Store {uid}",
        is_active=True,
        is_verified=True,
    )
    db.add(store)
    db.commit()
    db.refresh(store)
    return store


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


@pytest.fixture
def member_auth(member_owner, member_store, member_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=member_owner.id,
        email=member_owner.email,
        username=member_owner.username,
        role="merchant_owner",
        is_active=True,
        token_store_id=member_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 staff_role(db, member_store):
    """Create a 'staff' role for the store."""
    role = Role(
        store_id=member_store.id,
        name="staff",
        permissions=["orders.view", "products.view"],
    )
    db.add(role)
    db.commit()
    db.refresh(role)
    return role


@pytest.fixture
def manager_role(db, member_store):
    """Create a 'manager' role for the store."""
    role = Role(
        store_id=member_store.id,
        name="manager",
        permissions=["orders.view", "orders.edit", "products.view", "products.edit", "team.view"],
    )
    db.add(role)
    db.commit()
    db.refresh(role)
    return role


@pytest.fixture
def team_member_user(db):
    """Create another user to serve as a team member."""
    from middleware.auth import AuthManager

    auth = AuthManager()
    uid = uuid.uuid4().hex[:8]
    user = User(
        email=f"teammember_{uid}@test.com",
        username=f"teammember_{uid}",
        hashed_password=auth.hash_password("memberpass123"),
        role="store_member",
        is_active=True,
        first_name="Team",
        last_name="Member",
    )
    db.add(user)
    db.commit()
    db.refresh(user)
    return user


@pytest.fixture
def team_member(db, member_store, team_member_user, staff_role):
    """Create a StoreUser for team_member_user with staff role."""
    store_user = StoreUser(
        store_id=member_store.id,
        user_id=team_member_user.id,
        role_id=staff_role.id,
        is_active=True,
    )
    db.add(store_user)
    db.commit()
    db.refresh(store_user)
    return store_user


# ============================================================================
# GET /team/members
# ============================================================================


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

    def test_list_members_returns_owner_and_member(
        self, client, member_auth, team_member, member_owner, team_member_user
    ):
        """GET /members returns both owner and team member."""
        response = client.get(f"{BASE}/members", headers=member_auth)
        assert response.status_code == 200
        data = response.json()
        member_ids = {m["id"] for m in data["members"]}
        assert member_owner.id in member_ids
        assert team_member_user.id in member_ids

    def test_list_members_response_shape(self, client, member_auth, team_member):
        """Each member in the response has expected fields."""
        response = client.get(f"{BASE}/members", headers=member_auth)
        assert response.status_code == 200
        data = response.json()
        assert "members" in data
        assert "total" in data
        assert "active_count" in data
        assert "pending_invitations" in data
        member = data["members"][0]
        assert "id" in member
        assert "email" in member
        assert "username" in member
        assert "first_name" in member
        assert "last_name" in member
        assert "full_name" in member
        assert "role_name" in member
        assert "role_id" in member
        assert "permissions" in member
        assert "is_active" in member
        assert "is_owner" in member
        assert "invitation_pending" in member

    def test_list_members_stats(
        self, client, member_auth, team_member, member_owner
    ):
        """GET /members returns correct statistics."""
        response = client.get(f"{BASE}/members", headers=member_auth)
        assert response.status_code == 200
        data = response.json()
        assert data["total"] >= 2
        assert data["active_count"] >= 2
        assert data["pending_invitations"] >= 0


# ============================================================================
# GET /team/members/{user_id}
# ============================================================================


@pytest.mark.integration
@pytest.mark.tenancy
class TestGetMember:
    """Tests for GET /api/v1/store/team/members/{user_id}."""

    def test_get_member_success(
        self, client, member_auth, team_member, team_member_user
    ):
        """GET /members/{user_id} returns the specific member."""
        response = client.get(
            f"{BASE}/members/{team_member_user.id}", headers=member_auth
        )
        assert response.status_code == 200
        data = response.json()
        assert data["id"] == team_member_user.id
        assert data["email"] == team_member_user.email
        assert data["role_name"] == "staff"

    def test_get_nonexistent_member(self, client, member_auth):
        """GET /members/{user_id} returns 404 for non-existent user."""
        response = client.get(f"{BASE}/members/99999", headers=member_auth)
        assert response.status_code == 404


# ============================================================================
# PUT /team/members/{user_id}
# ============================================================================


@pytest.mark.integration
@pytest.mark.tenancy
class TestUpdateMember:
    """Tests for PUT /api/v1/store/team/members/{user_id}."""

    def test_update_member_role_success(
        self, client, member_auth, team_member, team_member_user, manager_role
    ):
        """PUT /members/{user_id} updates the member's role."""
        response = client.put(
            f"{BASE}/members/{team_member_user.id}",
            headers=member_auth,
            json={"role_id": manager_role.id},
        )
        assert response.status_code == 200
        data = response.json()
        assert data["role_name"] == "manager"
        assert data["role_id"] == manager_role.id

    def test_update_member_active_status(
        self, client, member_auth, team_member, team_member_user
    ):
        """PUT /members/{user_id} can deactivate a member."""
        response = client.put(
            f"{BASE}/members/{team_member_user.id}",
            headers=member_auth,
            json={"is_active": False},
        )
        assert response.status_code == 200
        data = response.json()
        assert data["is_active"] is False

    def test_update_owner_rejected(
        self, client, member_auth, member_owner, staff_role
    ):
        """PUT /members/{user_id} rejects changing owner's role."""
        response = client.put(
            f"{BASE}/members/{member_owner.id}",
            headers=member_auth,
            json={"role_id": staff_role.id},
        )
        assert response.status_code in (400, 422)

    def test_update_nonexistent_member(self, client, member_auth, staff_role):
        """PUT /members/{user_id} returns 404 for non-existent user."""
        response = client.put(
            f"{BASE}/members/99999",
            headers=member_auth,
            json={"role_id": staff_role.id},
        )
        assert response.status_code == 404

    def test_update_with_invalid_role_id(
        self, client, member_auth, team_member, team_member_user
    ):
        """PUT /members/{user_id} returns 422 for non-existent role."""
        response = client.put(
            f"{BASE}/members/{team_member_user.id}",
            headers=member_auth,
            json={"role_id": 99999},
        )
        assert response.status_code == 422


# ============================================================================
# DELETE /team/members/{user_id}
# ============================================================================


@pytest.mark.integration
@pytest.mark.tenancy
class TestRemoveMember:
    """Tests for DELETE /api/v1/store/team/members/{user_id}."""

    def test_remove_member_success(
        self, client, member_auth, team_member, team_member_user, db
    ):
        """DELETE /members/{user_id} removes a team member."""
        response = client.delete(
            f"{BASE}/members/{team_member_user.id}", headers=member_auth
        )
        assert response.status_code == 200
        data = response.json()
        assert data["user_id"] == team_member_user.id

        # Verify member is soft-deleted (deleted_at set, record hidden from normal queries)
        db.expire_all()
        store_user = (
            db.query(StoreUser)
            .execution_options(include_deleted=True)
            .filter(StoreUser.user_id == team_member_user.id)
            .first()
        )
        assert store_user is not None
        assert store_user.deleted_at is not None

    def test_remove_owner_rejected(self, client, member_auth, member_owner):
        """DELETE /members/{user_id} rejects removing the owner."""
        response = client.delete(
            f"{BASE}/members/{member_owner.id}", headers=member_auth
        )
        assert response.status_code == 400

    def test_remove_nonexistent_member(self, client, member_auth):
        """DELETE /members/{user_id} returns 404 for non-existent user."""
        response = client.delete(
            f"{BASE}/members/99999", headers=member_auth
        )
        assert response.status_code == 404


# ============================================================================
# POST /team/invite
# ============================================================================


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

    def test_invite_member_success(self, client, member_auth, staff_role):
        """POST /invite creates an invitation for a new email."""
        uid = uuid.uuid4().hex[:8]
        response = client.post(
            f"{BASE}/invite",
            headers=member_auth,
            json={
                "email": f"newinvite_{uid}@test.com",
                "role_name": "staff",
                "first_name": "New",
                "last_name": "Invitee",
            },
        )
        assert response.status_code == 200
        data = response.json()
        assert data["email"] == f"newinvite_{uid}@test.com"
        assert data["invitation_sent"] is True
        assert data["role"] == "staff"

    def test_invite_duplicate_email(
        self, client, member_auth, team_member, team_member_user, staff_role
    ):
        """POST /invite with existing member email returns error or reactivation."""
        response = client.post(
            f"{BASE}/invite",
            headers=member_auth,
            json={
                "email": team_member_user.email,
                "role_name": "staff",
            },
        )
        # May succeed as reactivation or fail as duplicate
        assert response.status_code in (200, 400, 409, 422)


# ============================================================================
# POST /team/members/{user_id}/resend
# ============================================================================


@pytest.mark.integration
@pytest.mark.tenancy
class TestResendInvitation:
    """Tests for POST /api/v1/store/team/members/{user_id}/resend."""

    def test_resend_invitation_for_pending_member(
        self, client, member_auth, member_store, db
    ):
        """POST /members/{user_id}/resend works for pending members."""
        from unittest.mock import patch

        from middleware.auth import AuthManager

        auth = AuthManager()
        uid = uuid.uuid4().hex[:8]
        # Create a pending user (is_active=False, has invitation_token)
        pending_user = User(
            email=f"pending_{uid}@test.com",
            username=f"pending_{uid}",
            hashed_password=auth.hash_password("pass123"),
            role="store_member",
            is_active=False,
        )
        db.add(pending_user)
        db.commit()

        from app.modules.tenancy.models import Role

        role = db.query(Role).filter(Role.store_id == member_store.id).first()
        if not role:
            role = Role(store_id=member_store.id, name="staff", permissions=[])
            db.add(role)
            db.commit()

        pending_su = StoreUser(
            store_id=member_store.id,
            user_id=pending_user.id,
            role_id=role.id,
            is_active=False,
            invitation_token=f"old_token_{uid}",
            invitation_sent_at=None,
        )
        db.add(pending_su)
        db.commit()

        with patch("app.modules.tenancy.services.store_team_service.StoreTeamService._send_invitation_email"):
            response = client.post(
                f"{BASE}/members/{pending_user.id}/resend",
                headers=member_auth,
            )
        assert response.status_code == 200
        data = response.json()
        assert data["invitation_sent"] is True

        # Verify token was regenerated
        db.expire_all()
        updated_su = (
            db.query(StoreUser)
            .execution_options(include_deleted=True)
            .filter(StoreUser.id == pending_su.id)
            .first()
        )
        assert updated_su.invitation_token != f"old_token_{uid}"
        assert updated_su.invitation_sent_at is not None

    def test_resend_invitation_nonexistent_user(self, client, member_auth):
        """POST /members/{user_id}/resend returns 404 for nonexistent user."""
        response = client.post(
            f"{BASE}/members/99999/resend",
            headers=member_auth,
        )
        assert response.status_code == 404
