from __future__ import annotations

from unittest.mock import AsyncMock, patch

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

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


@override_settings(BP_SUBCOMMANDS_V1=True)
class BPSubcommandTests(TransactionTestCase):
    def setUp(self):
        self.user = User.objects.create_user(
            username="bp-sub-user",
            email="bp-sub@example.com",
            password="x",
        )
        self.person = Person.objects.create(user=self.user, name="Sub 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,
        )
        for action_type, position in (("extract_bp", 0), ("save_document", 1)):
            CommandAction.objects.create(
                profile=self.profile,
                action_type=action_type,
                enabled=True,
                position=position,
            )

    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_parser_detects_set_and_remainder(self):
        parsed = parse_bp_subcommand("  #BP set#  addendum text  ")
        self.assertEqual("set", parsed.command)
        self.assertEqual("addendum text", parsed.remainder_text)

    def test_parser_detects_set_range(self):
        parsed = parse_bp_subcommand("#bp set range# now")
        self.assertEqual("set_range", parsed.command)

    def test_parser_detects_dot_prefix_forms(self):
        parsed = parse_bp_subcommand(".BP set addendum text")
        self.assertEqual("set", parsed.command)
        self.assertEqual("addendum text", parsed.remainder_text)
        parsed_range = parse_bp_subcommand(".bp set range")
        self.assertEqual("set_range", parsed_range.command)

    def test_set_standalone_uses_remainder_only(self):
        trigger = Message.objects.create(
            user=self.user,
            session=self.session,
            sender_uuid="me",
            text="#bp set# direct body",
            ts=1000,
            source_service="whatsapp",
            source_chat_id="120363402761690215",
        )
        with patch(
            "core.commands.handlers.bp.ai_runner.run_prompt", new=AsyncMock()
        ) as mocked_ai:
            result = async_to_sync(BPCommandHandler().execute)(
                self._ctx(trigger, trigger.text)
            )
        self.assertTrue(result.ok)
        mocked_ai.assert_not_awaited()
        doc = BusinessPlanDocument.objects.get(trigger_message=trigger)
        self.assertEqual("direct body", doc.content_markdown)
        self.assertEqual(
            "Generated from 1 message.", doc.structured_payload.get("annotation")
        )

    def test_set_reply_only_uses_anchor(self):
        anchor = Message.objects.create(
            user=self.user,
            session=self.session,
            sender_uuid="peer",
            text="anchor body",
            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#",
            ts=2000,
            source_service="whatsapp",
            source_chat_id="120363402761690215",
            reply_to=anchor,
        )
        result = async_to_sync(BPCommandHandler().execute)(
            self._ctx(trigger, trigger.text)
        )
        self.assertTrue(result.ok)
        doc = BusinessPlanDocument.objects.get(trigger_message=trigger)
        self.assertEqual("anchor body", doc.content_markdown)
        self.assertEqual(
            "Generated from 1 message.", doc.structured_payload.get("annotation")
        )

    def test_set_reply_plus_addendum_uses_divider(self):
        anchor = Message.objects.create(
            user=self.user,
            session=self.session,
            sender_uuid="peer",
            text="base body",
            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# extra text",
            ts=2000,
            source_service="whatsapp",
            source_chat_id="120363402761690215",
            reply_to=anchor,
        )
        result = async_to_sync(BPCommandHandler().execute)(
            self._ctx(trigger, trigger.text)
        )
        self.assertTrue(result.ok)
        doc = BusinessPlanDocument.objects.get(trigger_message=trigger)
        self.assertIn("base body", doc.content_markdown)
        self.assertIn("--- Addendum (newer message text) ---", doc.content_markdown)
        self.assertIn("extra text", doc.content_markdown)
        self.assertEqual(
            "Generated from 1 message + 1 addendum.",
            doc.structured_payload.get("annotation"),
        )

    def test_set_range_requires_reply(self):
        trigger = Message.objects.create(
            user=self.user,
            session=self.session,
            sender_uuid="me",
            text="#bp set range#",
            ts=3000,
            source_service="whatsapp",
            source_chat_id="120363402761690215",
        )
        result = async_to_sync(BPCommandHandler().execute)(
            self._ctx(trigger, trigger.text)
        )
        self.assertFalse(result.ok)
        self.assertEqual("failed", result.status)
        self.assertEqual("bp_set_range_requires_reply_target", result.error)

    def test_set_range_exports_text_only_lines(self):
        anchor = Message.objects.create(
            user=self.user,
            session=self.session,
            sender_uuid="peer",
            text="line 1",
            ts=1000,
            source_service="whatsapp",
            source_chat_id="120363402761690215",
        )
        Message.objects.create(
            user=self.user,
            session=self.session,
            sender_uuid="me",
            text="",
            ts=1500,
            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,
        )
        result = async_to_sync(BPCommandHandler().execute)(
            self._ctx(trigger, trigger.text)
        )
        self.assertTrue(result.ok)
        doc = BusinessPlanDocument.objects.get(trigger_message=trigger)
        self.assertEqual("line 1\n(no text)\n#bp set range#", doc.content_markdown)
        self.assertEqual(
            "Generated from 3 messages.", doc.structured_payload.get("annotation")
        )
