# tests/unit/core/test_soft_delete.py
"""
Unit tests for soft-delete infrastructure.

Tests the SoftDeleteMixin, automatic query filtering, and utility functions.
"""

import uuid
from datetime import UTC, datetime

import pytest

from app.core.soft_delete import restore, soft_delete, soft_delete_cascade
from app.modules.tenancy.models import Merchant, Store, StoreUser, User

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


@pytest.fixture
def sd_owner(db):
    """Create a user for soft-delete tests."""
    from middleware.auth import AuthManager

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


@pytest.fixture
def sd_merchant(db, sd_owner):
    """Create a merchant for soft-delete tests."""
    merchant = Merchant(
        name="SD Test Merchant",
        owner_user_id=sd_owner.id,
        contact_email=sd_owner.email,
        is_active=True,
        is_verified=True,
    )
    db.add(merchant)
    db.commit()
    db.refresh(merchant)
    return merchant


@pytest.fixture
def sd_store(db, sd_merchant):
    """Create a store for soft-delete tests."""
    uid = uuid.uuid4().hex[:8]
    store = Store(
        merchant_id=sd_merchant.id,
        store_code=f"SDTEST_{uid.upper()}",
        subdomain=f"sdtest{uid}",
        name=f"SD Test Store {uid}",
        is_active=True,
        is_verified=True,
    )
    db.add(store)
    db.commit()
    db.refresh(store)
    return store


@pytest.fixture
def sd_member_user(db):
    """Create another user to be a store member."""
    from middleware.auth import AuthManager

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


@pytest.fixture
def sd_store_user(db, sd_store, sd_member_user):
    """Create a StoreUser membership."""
    store_user = StoreUser(
        store_id=sd_store.id,
        user_id=sd_member_user.id,
        is_active=True,
    )
    db.add(store_user)
    db.commit()
    db.refresh(store_user)
    return store_user


# ============================================================================
# SoftDeleteMixin basic behavior
# ============================================================================


@pytest.mark.unit
class TestSoftDeleteBasic:
    """Test soft_delete() utility function."""

    def test_soft_delete_sets_fields(self, db, sd_owner, sd_store):
        """soft_delete() sets deleted_at and deleted_by_id."""
        soft_delete(db, sd_store, deleted_by_id=sd_owner.id)
        db.commit()

        # Query with include_deleted to see the record
        store = (
            db.query(Store)
            .execution_options(include_deleted=True)
            .filter(Store.id == sd_store.id)
            .first()
        )
        assert store is not None
        assert store.deleted_at is not None
        assert store.deleted_by_id == sd_owner.id

    def test_soft_deleted_excluded_from_queries(self, db, sd_owner, sd_store):
        """Soft-deleted records are automatically excluded from normal queries."""
        store_id = sd_store.id
        soft_delete(db, sd_store, deleted_by_id=sd_owner.id)
        db.commit()

        result = db.query(Store).filter(Store.id == store_id).first()
        assert result is None

    def test_soft_deleted_visible_with_include_deleted(self, db, sd_owner, sd_store):
        """Soft-deleted records are visible with include_deleted=True."""
        store_id = sd_store.id
        soft_delete(db, sd_store, deleted_by_id=sd_owner.id)
        db.commit()

        result = (
            db.query(Store)
            .execution_options(include_deleted=True)
            .filter(Store.id == store_id)
            .first()
        )
        assert result is not None
        assert result.id == store_id


# ============================================================================
# Restore
# ============================================================================


@pytest.mark.unit
class TestRestore:
    """Test restore() utility function."""

    def test_restore_clears_deleted_fields(self, db, sd_owner, sd_store):
        """restore() clears deleted_at and deleted_by_id."""
        soft_delete(db, sd_store, deleted_by_id=sd_owner.id)
        db.commit()

        restored = restore(db, Store, sd_store.id, restored_by_id=sd_owner.id)
        db.commit()

        assert restored.deleted_at is None
        assert restored.deleted_by_id is None

    def test_restore_makes_record_visible(self, db, sd_owner, sd_store):
        """After restore, record is visible in normal queries."""
        store_id = sd_store.id
        soft_delete(db, sd_store, deleted_by_id=sd_owner.id)
        db.commit()

        restore(db, Store, store_id, restored_by_id=sd_owner.id)
        db.commit()

        result = db.query(Store).filter(Store.id == store_id).first()
        assert result is not None

    def test_restore_not_deleted_raises(self, db, sd_store):
        """restore() raises ValueError if record is not deleted."""
        with pytest.raises(ValueError, match="is not deleted"):
            restore(db, Store, sd_store.id, restored_by_id=1)

    def test_restore_not_found_raises(self, db):
        """restore() raises ValueError if record doesn't exist."""
        with pytest.raises(ValueError, match="not found"):
            restore(db, Store, 99999, restored_by_id=1)


# ============================================================================
# Cascade soft delete
# ============================================================================


@pytest.mark.unit
class TestSoftDeleteCascade:
    """Test soft_delete_cascade() utility function."""

    def test_cascade_deletes_parent_and_children(
        self, db, sd_owner, sd_store, sd_store_user
    ):
        """soft_delete_cascade() deletes parent and its children."""
        count = soft_delete_cascade(
            db,
            sd_store,
            deleted_by_id=sd_owner.id,
            cascade_rels=[("store_users", [])],
        )
        db.commit()

        assert count == 2  # store + store_user

        # Both should be hidden from normal queries
        assert db.query(Store).filter(Store.id == sd_store.id).first() is None
        assert (
            db.query(StoreUser).filter(StoreUser.id == sd_store_user.id).first()
            is None
        )

        # Both visible with include_deleted
        store = (
            db.query(Store)
            .execution_options(include_deleted=True)
            .filter(Store.id == sd_store.id)
            .first()
        )
        assert store is not None
        assert store.deleted_at is not None

        su = (
            db.query(StoreUser)
            .execution_options(include_deleted=True)
            .filter(StoreUser.id == sd_store_user.id)
            .first()
        )
        assert su is not None
        assert su.deleted_at is not None


# ============================================================================
# Partial unique indexes
# ============================================================================


@pytest.mark.unit
class TestPartialUniqueIndexes:
    """Test that unique constraints allow reuse after soft delete."""

    def test_user_email_reusable_after_soft_delete(self, db):
        """Soft-deleted user's email can be used by a new user."""
        from middleware.auth import AuthManager

        auth = AuthManager()
        email = f"reuse_{uuid.uuid4().hex[:8]}@test.com"
        username = f"reuse_{uuid.uuid4().hex[:8]}"

        user1 = User(
            email=email,
            username=username,
            hashed_password=auth.hash_password("pass123"),
            role="store_member",
            is_active=True,
        )
        db.add(user1)
        db.commit()

        # Soft-delete user1
        soft_delete(db, user1, deleted_by_id=None)
        db.commit()

        # Create user2 with same email — should succeed
        user2 = User(
            email=email,
            username=f"reuse2_{uuid.uuid4().hex[:8]}",
            hashed_password=auth.hash_password("pass123"),
            role="store_member",
            is_active=True,
        )
        db.add(user2)
        db.commit()
        db.refresh(user2)

        assert user2.id is not None
        assert user2.email == email
