from __future__ import annotations

from unittest.mock import AsyncMock, patch

from asgiref.sync import async_to_sync
from django.test import TransactionTestCase

from core.commands.base import CommandContext
from core.commands.handlers.bp import BPCommandHandler
from core.models import (
    AI,
    BusinessPlanDocument,
    ChatSession,
    CommandAction,
    CommandChannelBinding,
    CommandProfile,
    CommandRun,
    Message,
    Person,
    PersonIdentifier,
    User,
)


class BPFallbackTests(TransactionTestCase):
    def setUp(self):
        self.user = User.objects.create_user(
            username="bp-fallback-user",
            email="bp-fallback@example.com",
            password="x",
        )
        self.person = Person.objects.create(user=self.user, name="Fallback Person")
        self.identifier = PersonIdentifier.objects.create(
            user=self.user,
            person=self.person,
            service="whatsapp",
            identifier="120363402761690215",
        )
        self.session = ChatSession.objects.create(
            user=self.user,
            identifier=self.identifier,
        )
        self.profile = CommandProfile.objects.create(
            user=self.user,
            slug="bp",
            name="Business Plan",
            enabled=True,
            trigger_token="#bp#",
            reply_required=True,
            exact_match_only=True,
        )
        CommandChannelBinding.objects.create(
            profile=self.profile,
            direction="ingress",
            service="whatsapp",
            channel_identifier="120363402761690215",
            enabled=True,
        )
        CommandChannelBinding.objects.create(
            profile=self.profile,
            direction="egress",
            service="web",
            channel_identifier="120363402761690215",
            enabled=True,
        )
        for action_type, position in (
            ("extract_bp", 0),
            ("save_document", 1),
            ("post_result", 2),
        ):
            CommandAction.objects.create(
                profile=self.profile,
                action_type=action_type,
                enabled=True,
                position=position,
            )
        AI.objects.create(
            user=self.user,
            base_url="https://example.invalid",
            api_key="test-key",
            model="gpt-4o-mini",
        )

    def test_bp_fails_fast_when_ai_fails(self):
        anchor = Message.objects.create(
            user=self.user,
            session=self.session,
            sender_uuid="peer",
            text="Anchor content",
            ts=1000,
            source_service="whatsapp",
            source_message_id="wa-anchor-1",
        )
        trigger = Message.objects.create(
            user=self.user,
            session=self.session,
            sender_uuid="peer",
            text="#bp#",
            ts=2000,
            source_service="whatsapp",
            source_message_id="wa-trigger-1",
            reply_to=anchor,
            reply_source_service="whatsapp",
            reply_source_message_id="wa-anchor-1",
        )

        with patch(
            "core.commands.handlers.bp.ai_runner.run_prompt",
            new=AsyncMock(side_effect=RuntimeError("quota")),
        ):
            result = async_to_sync(BPCommandHandler().execute)(
                CommandContext(
                    service="whatsapp",
                    channel_identifier="120363402761690215",
                    message_id=str(trigger.id),
                    user_id=self.user.id,
                    message_text="#bp#",
                    payload={},
                )
            )

        self.assertFalse(result.ok)
        run = CommandRun.objects.get(trigger_message=trigger, profile=self.profile)
        self.assertEqual("failed", run.status)
        self.assertIn("bp_ai_failed", str(run.error))
        self.assertFalse(
            BusinessPlanDocument.objects.filter(trigger_message=trigger).exists()
        )

    def test_bp_uses_same_ai_selection_order_as_compose(self):
        AI.objects.create(
            user=self.user,
            base_url="https://example.invalid",
            api_key="another-key",
            model="gpt-4o",
        )
        selected = AI.objects.filter(user=self.user).first()
        # Compose uses QuerySet.first() without explicit ordering; BP should match.
        self.assertIsNotNone(selected)
        self.assertEqual(self.profile.user_id, selected.user_id)
