from unittest.mock import patch

import pytest
from faker import Faker
from sqlalchemy.orm import Session

from models.enums import ConversationFromSource
from models.model import EndUser, Message
from models.web import SavedMessage
from services.app_service import AppService
from services.saved_message_service import SavedMessageService
from tests.test_containers_integration_tests.helpers import generate_valid_password


class TestSavedMessageService:
    """Integration tests for SavedMessageService using testcontainers."""

    @pytest.fixture
    def mock_external_service_dependencies(self):
        """Mock setup for external service dependencies."""
        with (
            patch("services.account_service.FeatureService") as mock_account_feature_service,
            patch("services.app_service.ModelManager.for_tenant") as mock_model_manager,
            patch("services.saved_message_service.MessageService") as mock_message_service,
        ):
            # Setup default mock returns
            mock_account_feature_service.get_system_features.return_value.is_allow_register = True

            # Mock ModelManager for app creation
            mock_model_instance = mock_model_manager.return_value
            mock_model_instance.get_default_model_instance.return_value = None
            mock_model_instance.get_default_provider_model_name.return_value = ("openai", "gpt-3.5-turbo")

            # Mock MessageService
            mock_message_service.get_message.return_value = None
            mock_message_service.pagination_by_last_id.return_value = None

            yield {
                "account_feature_service": mock_account_feature_service,
                "model_manager": mock_model_manager,
                "message_service": mock_message_service,
            }

    def _create_test_app_and_account(self, db_session_with_containers: Session, mock_external_service_dependencies):
        """
        Helper method to create a test app and account for testing.

        Args:
            db_session_with_containers: Database session from testcontainers infrastructure
            mock_external_service_dependencies: Mock dependencies

        Returns:
            tuple: (app, account) - Created app and account instances
        """
        fake = Faker()

        # Setup mocks for account creation
        mock_external_service_dependencies[
            "account_feature_service"
        ].get_system_features.return_value.is_allow_register = True

        # Create account and tenant first
        from services.account_service import AccountService, TenantService

        account = AccountService.create_account(
            email=fake.email(),
            name=fake.name(),
            interface_language="en-US",
            password=generate_valid_password(fake),
        )
        TenantService.create_owner_tenant_if_not_exist(account, name=fake.company())
        tenant = account.current_tenant

        # Create app with realistic data
        app_args = {
            "name": fake.company(),
            "description": fake.text(max_nb_chars=100),
            "mode": "chat",
            "icon_type": "emoji",
            "icon": "🤖",
            "icon_background": "#FF6B6B",
            "api_rph": 100,
            "api_rpm": 10,
        }

        app_service = AppService()
        app = app_service.create_app(tenant.id, app_args, account)

        return app, account

    def _create_test_end_user(self, db_session_with_containers: Session, app):
        """
        Helper method to create a test end user for testing.

        Args:
            db_session_with_containers: Database session from testcontainers infrastructure
            app: App instance to associate the end user with

        Returns:
            EndUser: Created end user instance
        """
        fake = Faker()

        end_user = EndUser(
            tenant_id=app.tenant_id,
            app_id=app.id,
            external_user_id=fake.uuid4(),
            name=fake.name(),
            type="normal",
            session_id=fake.uuid4(),
            is_anonymous=False,
        )

        db_session_with_containers.add(end_user)
        db_session_with_containers.commit()

        return end_user

    def _create_test_message(self, db_session_with_containers: Session, app, user):
        """
        Helper method to create a test message for testing.

        Args:
            db_session_with_containers: Database session from testcontainers infrastructure
            app: App instance to associate the message with
            user: User instance (Account or EndUser) to associate the message with

        Returns:
            Message: Created message instance
        """
        fake = Faker()

        # Create a simple conversation first
        from models.model import Conversation

        is_account = hasattr(user, "current_tenant")
        from_source = ConversationFromSource.CONSOLE if is_account else ConversationFromSource.API

        conversation = Conversation(
            app_id=app.id,
            from_source=from_source,
            from_end_user_id=user.id if not is_account else None,
            from_account_id=user.id if is_account else None,
            name=fake.sentence(nb_words=3),
            inputs={},
            status="normal",
            mode="chat",
        )

        db_session_with_containers.add(conversation)
        db_session_with_containers.commit()

        # Create message
        message = Message(
            app_id=app.id,
            conversation_id=conversation.id,
            from_source=from_source,
            from_end_user_id=user.id if not is_account else None,
            from_account_id=user.id if is_account else None,
            inputs={},
            query=fake.sentence(nb_words=5),
            message=fake.text(max_nb_chars=100),
            answer=fake.text(max_nb_chars=200),
            message_tokens=50,
            answer_tokens=100,
            message_unit_price=0.001,
            answer_unit_price=0.002,
            total_price=0.003,
            currency="USD",
            status="normal",
        )

        db_session_with_containers.add(message)
        db_session_with_containers.commit()

        return message

    def test_pagination_by_last_id_success_with_account_user(
        self, db_session_with_containers: Session, mock_external_service_dependencies
    ):
        """
        Test successful pagination by last ID with account user.

        This test verifies:
        - Proper pagination with account user
        - Correct filtering by app_id and user
        - Proper role identification for account users
        - MessageService integration
        """
        # Arrange: Create test data
        fake = Faker()
        app, account = self._create_test_app_and_account(db_session_with_containers, mock_external_service_dependencies)

        # Create test messages
        message1 = self._create_test_message(db_session_with_containers, app, account)
        message2 = self._create_test_message(db_session_with_containers, app, account)

        # Create saved messages
        saved_message1 = SavedMessage(
            app_id=app.id,
            message_id=message1.id,
            created_by_role="account",
            created_by=account.id,
        )
        saved_message2 = SavedMessage(
            app_id=app.id,
            message_id=message2.id,
            created_by_role="account",
            created_by=account.id,
        )

        db_session_with_containers.add_all([saved_message1, saved_message2])
        db_session_with_containers.commit()

        # Mock MessageService.pagination_by_last_id return value
        from libs.infinite_scroll_pagination import InfiniteScrollPagination

        mock_pagination = InfiniteScrollPagination(data=[message1, message2], limit=10, has_more=False)
        mock_external_service_dependencies["message_service"].pagination_by_last_id.return_value = mock_pagination

        # Act: Execute the method under test
        result = SavedMessageService.pagination_by_last_id(app_model=app, user=account, last_id=None, limit=10)

        # Assert: Verify the expected outcomes
        assert result is not None
        assert result.data == [message1, message2]
        assert result.limit == 10
        assert result.has_more is False

        # Verify MessageService was called with correct parameters
        # Sort the IDs to handle database query order variations
        expected_include_ids = sorted([message1.id, message2.id])
        actual_call = mock_external_service_dependencies["message_service"].pagination_by_last_id.call_args
        actual_include_ids = sorted(actual_call.kwargs.get("include_ids", []))

        assert actual_call.kwargs["app_model"] == app
        assert actual_call.kwargs["user"] == account
        assert actual_call.kwargs["last_id"] is None
        assert actual_call.kwargs["limit"] == 10
        assert actual_include_ids == expected_include_ids

        # Verify database state
        db_session_with_containers.refresh(saved_message1)
        db_session_with_containers.refresh(saved_message2)
        assert saved_message1.id is not None
        assert saved_message2.id is not None
        assert saved_message1.created_by_role == "account"
        assert saved_message2.created_by_role == "account"

    def test_pagination_by_last_id_success_with_end_user(
        self, db_session_with_containers: Session, mock_external_service_dependencies
    ):
        """
        Test successful pagination by last ID with end user.

        This test verifies:
        - Proper pagination with end user
        - Correct filtering by app_id and user
        - Proper role identification for end users
        - MessageService integration
        """
        # Arrange: Create test data
        fake = Faker()
        app, account = self._create_test_app_and_account(db_session_with_containers, mock_external_service_dependencies)
        end_user = self._create_test_end_user(db_session_with_containers, app)

        # Create test messages
        message1 = self._create_test_message(db_session_with_containers, app, end_user)
        message2 = self._create_test_message(db_session_with_containers, app, end_user)

        # Create saved messages
        saved_message1 = SavedMessage(
            app_id=app.id,
            message_id=message1.id,
            created_by_role="end_user",
            created_by=end_user.id,
        )
        saved_message2 = SavedMessage(
            app_id=app.id,
            message_id=message2.id,
            created_by_role="end_user",
            created_by=end_user.id,
        )

        db_session_with_containers.add_all([saved_message1, saved_message2])
        db_session_with_containers.commit()

        # Mock MessageService.pagination_by_last_id return value
        from libs.infinite_scroll_pagination import InfiniteScrollPagination

        mock_pagination = InfiniteScrollPagination(data=[message1, message2], limit=5, has_more=True)
        mock_external_service_dependencies["message_service"].pagination_by_last_id.return_value = mock_pagination

        # Act: Execute the method under test
        result = SavedMessageService.pagination_by_last_id(
            app_model=app, user=end_user, last_id="test_last_id", limit=5
        )

        # Assert: Verify the expected outcomes
        assert result is not None
        assert result.data == [message1, message2]
        assert result.limit == 5
        assert result.has_more is True

        # Verify MessageService was called with correct parameters
        # Sort the IDs to handle database query order variations
        expected_include_ids = sorted([message1.id, message2.id])
        actual_call = mock_external_service_dependencies["message_service"].pagination_by_last_id.call_args
        actual_include_ids = sorted(actual_call.kwargs.get("include_ids", []))

        assert actual_call.kwargs["app_model"] == app
        assert actual_call.kwargs["user"] == end_user
        assert actual_call.kwargs["last_id"] == "test_last_id"
        assert actual_call.kwargs["limit"] == 5
        assert actual_include_ids == expected_include_ids

        # Verify database state
        db_session_with_containers.refresh(saved_message1)
        db_session_with_containers.refresh(saved_message2)
        assert saved_message1.id is not None
        assert saved_message2.id is not None
        assert saved_message1.created_by_role == "end_user"
        assert saved_message2.created_by_role == "end_user"

    def test_save_success_with_new_message(
        self, db_session_with_containers: Session, mock_external_service_dependencies
    ):
        """
        Test successful save of a new message.

        This test verifies:
        - Proper creation of new saved message
        - Correct database state after save
        - Proper relationship establishment
        - MessageService integration for message retrieval
        """
        # Arrange: Create test data
        fake = Faker()
        app, account = self._create_test_app_and_account(db_session_with_containers, mock_external_service_dependencies)
        message = self._create_test_message(db_session_with_containers, app, account)

        # Mock MessageService.get_message return value
        mock_external_service_dependencies["message_service"].get_message.return_value = message

        # Act: Execute the method under test
        SavedMessageService.save(app_model=app, user=account, message_id=message.id)

        # Assert: Verify the expected outcomes
        # Check if saved message was created in database

        saved_message = (
            db_session_with_containers.query(SavedMessage)
            .where(
                SavedMessage.app_id == app.id,
                SavedMessage.message_id == message.id,
                SavedMessage.created_by_role == "account",
                SavedMessage.created_by == account.id,
            )
            .first()
        )

        assert saved_message is not None
        assert saved_message.app_id == app.id
        assert saved_message.message_id == message.id
        assert saved_message.created_by_role == "account"
        assert saved_message.created_by == account.id
        assert saved_message.created_at is not None

        # Verify MessageService.get_message was called
        mock_external_service_dependencies["message_service"].get_message.assert_called_once_with(
            app_model=app, user=account, message_id=message.id
        )

        # Verify database state
        db_session_with_containers.refresh(saved_message)
        assert saved_message.id is not None

    def test_pagination_by_last_id_error_no_user(
        self, db_session_with_containers: Session, mock_external_service_dependencies
    ):
        """
        Test error handling when no user is provided.

        This test verifies:
        - Proper error handling for missing user
        - ValueError is raised when user is None
        - No database operations are performed
        """
        # Arrange: Create test data
        fake = Faker()
        app, account = self._create_test_app_and_account(db_session_with_containers, mock_external_service_dependencies)

        # Act & Assert: Verify proper error handling
        with pytest.raises(ValueError) as exc_info:
            SavedMessageService.pagination_by_last_id(app_model=app, user=None, last_id=None, limit=10)

        assert "User is required" in str(exc_info.value)

    def test_save_error_no_user(self, db_session_with_containers: Session, mock_external_service_dependencies):
        """
        Test error handling when saving message with no user.

        This test verifies:
        - Method returns early when user is None
        - No database operations are performed
        - No exceptions are raised
        """
        # Arrange: Create test data
        fake = Faker()
        app, account = self._create_test_app_and_account(db_session_with_containers, mock_external_service_dependencies)
        message = self._create_test_message(db_session_with_containers, app, account)

        # Act: Execute the method under test with None user
        result = SavedMessageService.save(app_model=app, user=None, message_id=message.id)

        # Assert: Verify the expected outcomes
        assert result is None

        # Verify no saved message was created

        saved_message = (
            db_session_with_containers.query(SavedMessage)
            .where(
                SavedMessage.app_id == app.id,
                SavedMessage.message_id == message.id,
            )
            .first()
        )

        assert saved_message is None

    def test_delete_success_existing_message(
        self, db_session_with_containers: Session, mock_external_service_dependencies
    ):
        """
        Test successful deletion of an existing saved message.

        This test verifies:
        - Proper deletion of existing saved message
        - Correct database state after deletion
        - No errors during deletion process
        """
        # Arrange: Create test data
        fake = Faker()
        app, account = self._create_test_app_and_account(db_session_with_containers, mock_external_service_dependencies)
        message = self._create_test_message(db_session_with_containers, app, account)

        # Create a saved message first
        saved_message = SavedMessage(
            app_id=app.id,
            message_id=message.id,
            created_by_role="account",
            created_by=account.id,
        )

        db_session_with_containers.add(saved_message)
        db_session_with_containers.commit()

        # Verify saved message exists
        assert (
            db_session_with_containers.query(SavedMessage)
            .where(
                SavedMessage.app_id == app.id,
                SavedMessage.message_id == message.id,
                SavedMessage.created_by_role == "account",
                SavedMessage.created_by == account.id,
            )
            .first()
            is not None
        )

        # Act: Execute the method under test
        SavedMessageService.delete(app_model=app, user=account, message_id=message.id)

        # Assert: Verify the expected outcomes
        # Check if saved message was deleted from database
        deleted_saved_message = (
            db_session_with_containers.query(SavedMessage)
            .where(
                SavedMessage.app_id == app.id,
                SavedMessage.message_id == message.id,
                SavedMessage.created_by_role == "account",
                SavedMessage.created_by == account.id,
            )
            .first()
        )

        assert deleted_saved_message is None

        # Verify database state
        db_session_with_containers.commit()
        # The message should still exist, only the saved_message should be deleted
        assert db_session_with_containers.query(Message).where(Message.id == message.id).first() is not None

    def test_save_for_end_user(self, db_session_with_containers: Session, mock_external_service_dependencies):
        """Test saving a message for an EndUser."""
        app, account = self._create_test_app_and_account(db_session_with_containers, mock_external_service_dependencies)
        end_user = self._create_test_end_user(db_session_with_containers, app)
        message = self._create_test_message(db_session_with_containers, app, end_user)

        mock_external_service_dependencies["message_service"].get_message.return_value = message

        SavedMessageService.save(app_model=app, user=end_user, message_id=message.id)

        saved = (
            db_session_with_containers.query(SavedMessage)
            .where(SavedMessage.app_id == app.id, SavedMessage.message_id == message.id)
            .first()
        )
        assert saved is not None
        assert saved.created_by == end_user.id
        assert saved.created_by_role == "end_user"

    def test_save_duplicate_is_idempotent(
        self, db_session_with_containers: Session, mock_external_service_dependencies
    ):
        """Test that saving an already-saved message does not create a duplicate."""
        app, account = self._create_test_app_and_account(db_session_with_containers, mock_external_service_dependencies)
        message = self._create_test_message(db_session_with_containers, app, account)

        mock_external_service_dependencies["message_service"].get_message.return_value = message

        # Save once
        SavedMessageService.save(app_model=app, user=account, message_id=message.id)
        # Save again
        SavedMessageService.save(app_model=app, user=account, message_id=message.id)

        count = (
            db_session_with_containers.query(SavedMessage)
            .where(SavedMessage.app_id == app.id, SavedMessage.message_id == message.id)
            .count()
        )
        assert count == 1

    def test_delete_without_user_does_nothing(
        self, db_session_with_containers: Session, mock_external_service_dependencies
    ):
        """Test that deleting without a user is a no-op."""
        app, account = self._create_test_app_and_account(db_session_with_containers, mock_external_service_dependencies)
        message = self._create_test_message(db_session_with_containers, app, account)

        # Pre-create a saved message
        saved = SavedMessage(app_id=app.id, message_id=message.id, created_by_role="account", created_by=account.id)
        db_session_with_containers.add(saved)
        db_session_with_containers.commit()

        SavedMessageService.delete(app_model=app, user=None, message_id=message.id)

        # Should still exist
        assert (
            db_session_with_containers.query(SavedMessage)
            .where(SavedMessage.app_id == app.id, SavedMessage.message_id == message.id)
            .first()
            is not None
        )

    def test_delete_non_existent_does_nothing(
        self, db_session_with_containers: Session, mock_external_service_dependencies
    ):
        """Test that deleting a non-existent saved message is a no-op."""
        app, account = self._create_test_app_and_account(db_session_with_containers, mock_external_service_dependencies)

        # Should not raise — use a valid UUID that doesn't exist in DB
        from uuid import uuid4

        SavedMessageService.delete(app_model=app, user=account, message_id=str(uuid4()))

    def test_delete_for_end_user(self, db_session_with_containers: Session, mock_external_service_dependencies):
        """Test deleting a saved message for an EndUser."""
        app, account = self._create_test_app_and_account(db_session_with_containers, mock_external_service_dependencies)
        end_user = self._create_test_end_user(db_session_with_containers, app)
        message = self._create_test_message(db_session_with_containers, app, end_user)

        saved = SavedMessage(app_id=app.id, message_id=message.id, created_by_role="end_user", created_by=end_user.id)
        db_session_with_containers.add(saved)
        db_session_with_containers.commit()

        SavedMessageService.delete(app_model=app, user=end_user, message_id=message.id)

        assert (
            db_session_with_containers.query(SavedMessage)
            .where(SavedMessage.app_id == app.id, SavedMessage.message_id == message.id)
            .first()
            is None
        )

    def test_delete_only_affects_own_saved_messages(
        self, db_session_with_containers: Session, mock_external_service_dependencies
    ):
        """Test that delete only removes the requesting user's saved message."""
        app, account1 = self._create_test_app_and_account(
            db_session_with_containers, mock_external_service_dependencies
        )
        end_user = self._create_test_end_user(db_session_with_containers, app)
        message = self._create_test_message(db_session_with_containers, app, account1)

        # Both users save the same message
        saved_account = SavedMessage(
            app_id=app.id, message_id=message.id, created_by_role="account", created_by=account1.id
        )
        saved_end_user = SavedMessage(
            app_id=app.id, message_id=message.id, created_by_role="end_user", created_by=end_user.id
        )
        db_session_with_containers.add_all([saved_account, saved_end_user])
        db_session_with_containers.commit()

        # Delete only account1's saved message
        SavedMessageService.delete(app_model=app, user=account1, message_id=message.id)

        # Account's saved message should be gone
        assert (
            db_session_with_containers.query(SavedMessage)
            .where(
                SavedMessage.app_id == app.id,
                SavedMessage.message_id == message.id,
                SavedMessage.created_by == account1.id,
            )
            .first()
            is None
        )
        # End user's saved message should still exist
        assert (
            db_session_with_containers.query(SavedMessage)
            .where(
                SavedMessage.app_id == app.id,
                SavedMessage.message_id == message.id,
                SavedMessage.created_by == end_user.id,
            )
            .first()
            is not None
        )
