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.commands.policies import ensure_variant_policies_for_profile
from core.models import (
    AI,
    BusinessPlanDocument,
    ChatSession,
    CommandAction,
    CommandChannelBinding,
    CommandProfile,
    CommandVariantPolicy,
    Message,
    Person,
    PersonIdentifier,
    User,
)


class CommandVariantPolicyTests(TransactionTestCase):
    def setUp(self):
        self.user = User.objects.create_user(
            username="variant-user",
            email="variant@example.com",
            password="x",
        )
        self.person = Person.objects.create(user=self.user, name="Variant 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,
            visibility_mode="status_in_source",
            template_text="TEMPLATE SHOULD NOT LEAK INTO bp set",
        )
        AI.objects.create(
            user=self.user,
            base_url="https://example.invalid",
            api_key="test-key",
            model="gpt-4o-mini",
        )
        CommandAction.objects.create(
            profile=self.profile,
            action_type="extract_bp",
            enabled=True,
            position=0,
        )
        CommandAction.objects.create(
            profile=self.profile,
            action_type="save_document",
            enabled=True,
            position=1,
        )
        CommandAction.objects.create(
            profile=self.profile,
            action_type="post_result",
            enabled=True,
            position=2,
        )
        CommandChannelBinding.objects.create(
            profile=self.profile,
            direction="ingress",
            service="whatsapp",
            channel_identifier="120363402761690215",
            enabled=True,
        )
        CommandChannelBinding.objects.create(
            profile=self.profile,
            direction="egress",
            service="whatsapp",
            channel_identifier="120363402761690215",
            enabled=True,
        )

    def _ctx(self, trigger: Message, text: str) -> CommandContext:
        return CommandContext(
            service="whatsapp",
            channel_identifier="120363402761690215",
            message_id=str(trigger.id),
            user_id=self.user.id,
            message_text=text,
            payload={},
        )

    def test_ensure_variant_policies_backfills_bp_defaults(self):
        rows = ensure_variant_policies_for_profile(self.profile)
        self.assertSetEqual(set(rows.keys()), {"bp", "bp_set", "bp_set_range"})
        self.assertEqual("ai", rows["bp"].generation_mode)
        self.assertEqual("verbatim", rows["bp_set"].generation_mode)
        self.assertEqual("verbatim", rows["bp_set_range"].generation_mode)
        self.assertTrue(rows["bp"].send_plan_to_egress)
        self.assertTrue(rows["bp"].send_status_to_source)

    def test_bp_primary_can_run_in_verbatim_mode_without_ai(self):
        ensure_variant_policies_for_profile(self.profile)
        policy = CommandVariantPolicy.objects.get(
            profile=self.profile, variant_key="bp"
        )
        policy.generation_mode = "verbatim"
        policy.send_plan_to_egress = False
        policy.send_status_to_source = False
        policy.send_status_to_egress = False
        policy.save()

        anchor = Message.objects.create(
            user=self.user,
            session=self.session,
            sender_uuid="peer",
            text="anchor line",
            ts=1000,
            source_service="whatsapp",
            source_chat_id="120363402761690215",
        )
        trigger = Message.objects.create(
            user=self.user,
            session=self.session,
            sender_uuid="me",
            text="#bp#",
            ts=2000,
            source_service="whatsapp",
            source_chat_id="120363402761690215",
            reply_to=anchor,
        )

        result = async_to_sync(BPCommandHandler().execute)(self._ctx(trigger, "#bp#"))
        self.assertTrue(result.ok)
        doc = BusinessPlanDocument.objects.get(trigger_message=trigger)
        self.assertEqual("anchor line\n#bp#", doc.content_markdown)

    def test_bp_set_ai_mode_ignores_template(self):
        ensure_variant_policies_for_profile(self.profile)
        policy = CommandVariantPolicy.objects.get(
            profile=self.profile, variant_key="bp_set"
        )
        policy.generation_mode = "ai"
        policy.send_plan_to_egress = False
        policy.send_status_to_source = False
        policy.send_status_to_egress = False
        policy.save()

        trigger = Message.objects.create(
            user=self.user,
            session=self.session,
            sender_uuid="me",
            text="#bp set# text to transform",
            ts=1000,
            source_service="whatsapp",
            source_chat_id="120363402761690215",
        )

        with patch(
            "core.commands.handlers.bp.ai_runner.run_prompt",
            new=AsyncMock(return_value="AI RESULT"),
        ) as mocked:
            result = async_to_sync(BPCommandHandler().execute)(
                self._ctx(trigger, trigger.text)
            )

        self.assertTrue(result.ok)
        doc = BusinessPlanDocument.objects.get(trigger_message=trigger)
        self.assertEqual("AI RESULT", doc.content_markdown)
        call_args = mocked.await_args.args
        prompt_payload = call_args[0]
        self.assertNotIn("TEMPLATE SHOULD NOT LEAK", str(prompt_payload))

    def test_delivery_flags_control_source_and_egress_status(self):
        ensure_variant_policies_for_profile(self.profile)
        policy = CommandVariantPolicy.objects.get(
            profile=self.profile,
            variant_key="bp_set_range",
        )
        policy.generation_mode = "verbatim"
        policy.store_document = False
        policy.send_plan_to_egress = False
        policy.send_status_to_source = True
        policy.send_status_to_egress = True
        policy.save()

        anchor = Message.objects.create(
            user=self.user,
            session=self.session,
            sender_uuid="peer",
            text="line one",
            ts=1000,
            source_service="whatsapp",
            source_chat_id="120363402761690215",
        )
        trigger = Message.objects.create(
            user=self.user,
            session=self.session,
            sender_uuid="me",
            text="#bp set range#",
            ts=2000,
            source_service="whatsapp",
            source_chat_id="120363402761690215",
            reply_to=anchor,
        )

        with patch(
            "core.commands.handlers.bp.post_status_in_source",
            new=AsyncMock(return_value=True),
        ) as source_status, patch(
            "core.commands.handlers.bp.post_to_channel_binding",
            new=AsyncMock(return_value=True),
        ) as binding_send:
            result = async_to_sync(BPCommandHandler().execute)(
                self._ctx(trigger, trigger.text)
            )

        self.assertTrue(result.ok)
        source_status.assert_awaited()
        self.assertEqual(1, binding_send.await_count)
        self.assertFalse(
            BusinessPlanDocument.objects.filter(trigger_message=trigger).exists()
        )
