from __future__ import annotations

from unittest.mock import AsyncMock, patch

from django.test import TestCase
from django.urls import reverse

from core.models import (
    ChatSession,
    ChatTaskSource,
    DerivedTask,
    DerivedTaskEvent,
    Message,
    Person,
    PersonIdentifier,
    TaskEpic,
    TaskProject,
    User,
)


class TasksPagesManagementTests(TestCase):
    def setUp(self):
        self.user = User.objects.create_user(
            "tasks-pages-user", "tasks-pages@example.com", "x"
        )
        self.client.force_login(self.user)
        self.person = Person.objects.create(user=self.user, name="Scope Person")
        self.pid_whatsapp = PersonIdentifier.objects.create(
            user=self.user,
            person=self.person,
            service="whatsapp",
            identifier="120363402761690215@g.us",
        )
        self.pid_signal = PersonIdentifier.objects.create(
            user=self.user,
            person=self.person,
            service="signal",
            identifier="+15551230000",
        )

    def test_tasks_hub_can_create_project_name_only(self):
        response = self.client.post(
            reverse("tasks_hub"),
            {"action": "project_create", "name": "Ops"},
            follow=True,
        )
        self.assertEqual(200, response.status_code)
        project = TaskProject.objects.get(user=self.user, name="Ops")
        self.assertIsNotNone(project)
        self.assertFalse(
            ChatTaskSource.objects.filter(user=self.user, project=project).exists()
        )

    def test_tasks_hub_can_map_identifier_to_selected_project(self):
        project = TaskProject.objects.create(user=self.user, name="Mapped")
        response = self.client.post(
            reverse("tasks_hub"),
            {
                "action": "project_map_identifier",
                "project_id": str(project.id),
                "person_identifier_id": str(self.pid_signal.id),
                "person": str(self.person.id),
                "service": "whatsapp",
                "identifier": "120363402761690215@g.us",
            },
            follow=True,
        )
        self.assertEqual(200, response.status_code)
        self.assertTrue(
            ChatTaskSource.objects.filter(
                user=self.user,
                project=project,
                service="signal",
                channel_identifier="+15551230000",
                enabled=True,
            ).exists()
        )

    def test_tasks_hub_settings_link_preserves_scope_context(self):
        response = self.client.get(
            f"{reverse('tasks_hub')}?person={self.person.id}&service=signal&identifier=147e75bd-91b7-4014-b9e5-12a44b978f7b"
        )
        self.assertEqual(200, response.status_code)
        self.assertContains(
            response,
            f"{reverse('tasks_settings')}?person={self.person.id}&service=signal&identifier=147e75bd-91b7-4014-b9e5-12a44b978f7b",
        )

    def test_project_page_can_create_and_delete_epic(self):
        project = TaskProject.objects.create(user=self.user, name="Roadmap")
        create_response = self.client.post(
            reverse("tasks_project", kwargs={"project_id": str(project.id)}),
            {
                "action": "epic_create",
                "name": "Phase 1",
            },
            follow=True,
        )
        self.assertEqual(200, create_response.status_code)
        epic = TaskEpic.objects.get(project=project, name="Phase 1")
        self.assertIsNotNone(epic)

        delete_response = self.client.post(
            reverse("tasks_project", kwargs={"project_id": str(project.id)}),
            {
                "action": "epic_delete",
                "epic_id": str(epic.id),
            },
            follow=True,
        )
        self.assertEqual(200, delete_response.status_code)
        self.assertFalse(
            TaskEpic.objects.filter(project=project, name="Phase 1").exists()
        )

    def test_project_page_can_assign_and_clear_task_epic(self):
        project = TaskProject.objects.create(user=self.user, name="Roadmap")
        epic = TaskEpic.objects.create(project=project, name="Sprint A")
        session = ChatSession.objects.create(user=self.user, identifier=self.pid_signal)
        origin = Message.objects.create(
            user=self.user,
            session=session,
            ts=1_700_000_000_100,
            text="task: assign epic",
            sender_uuid="+15551230000",
            custom_author="OTHER",
            source_service="signal",
            source_chat_id="+15551230000",
        )
        task = DerivedTask.objects.create(
            user=self.user,
            project=project,
            title="Assign me",
            source_service="signal",
            source_channel="+15551230000",
            origin_message=origin,
            reference_code="9",
            status_snapshot="open",
        )
        assign_response = self.client.post(
            reverse("tasks_project", kwargs={"project_id": str(project.id)}),
            {
                "action": "task_set_epic",
                "task_id": str(task.id),
                "epic_id": str(epic.id),
            },
            follow=True,
        )
        self.assertEqual(200, assign_response.status_code)
        task.refresh_from_db()
        self.assertEqual(epic.id, task.epic_id)

        clear_response = self.client.post(
            reverse("tasks_project", kwargs={"project_id": str(project.id)}),
            {
                "action": "task_set_epic",
                "task_id": str(task.id),
                "epic_id": "",
            },
            follow=True,
        )
        self.assertEqual(200, clear_response.status_code)
        task.refresh_from_db()
        self.assertIsNone(task.epic_id)

    @patch("core.views.tasks.send_message_raw", new_callable=AsyncMock)
    def test_project_epic_create_announces_to_project_chats(self, mocked_send):
        project = TaskProject.objects.create(user=self.user, name="Roadmap")
        ChatTaskSource.objects.create(
            user=self.user,
            service="whatsapp",
            channel_identifier="120363402761690215@g.us",
            project=project,
            enabled=True,
        )
        response = self.client.post(
            reverse("tasks_project", kwargs={"project_id": str(project.id)}),
            {
                "action": "epic_create",
                "name": "Phase 2",
            },
            follow=True,
        )
        self.assertEqual(200, response.status_code)
        self.assertTrue(
            TaskEpic.objects.filter(project=project, name="Phase 2").exists()
        )
        self.assertTrue(mocked_send.await_count >= 1)
        payloads = [
            str(call.kwargs.get("text") or "") for call in mocked_send.await_args_list
        ]
        self.assertTrue(any("whatsapp usage" in row.lower() for row in payloads))
        self.assertTrue(any("add task to epic" in row.lower() for row in payloads))

    def test_group_page_create_and_map_project(self):
        response = self.client.post(
            reverse(
                "tasks_group",
                kwargs={
                    "service": "whatsapp",
                    "identifier": "120363402761690215@g.us",
                },
            ),
            {
                "action": "group_project_create",
                "project_name": "Group Project",
                "epic_name": "Sprint A",
            },
            follow=True,
        )
        self.assertEqual(200, response.status_code)
        project = TaskProject.objects.get(user=self.user, name="Group Project")
        epic = TaskEpic.objects.get(project=project, name="Sprint A")
        self.assertTrue(
            ChatTaskSource.objects.filter(
                user=self.user,
                service="whatsapp",
                channel_identifier="120363402761690215@g.us",
                project=project,
                epic=epic,
                enabled=True,
            ).exists()
        )

    def test_group_page_does_not_seed_source_for_malformed_whatsapp_asset_path(self):
        response = self.client.get(
            reverse(
                "tasks_group",
                kwargs={
                    "service": "whatsapp",
                    "identifier": "447777695114/static/js/template_profiler.js",
                },
            )
        )
        self.assertEqual(200, response.status_code)
        self.assertFalse(
            ChatTaskSource.objects.filter(
                user=self.user,
                service="whatsapp",
                channel_identifier__icontains="template_profiler.js",
            ).exists()
        )

    def test_group_page_does_not_seed_source_for_malformed_signal_asset_path(self):
        response = self.client.get(
            reverse(
                "tasks_group",
                kwargs={
                    "service": "signal",
                    "identifier": "group.c0VHQTlGMEhRL2V5/static/js/template_profiler.js",
                },
            )
        )
        self.assertEqual(200, response.status_code)
        self.assertFalse(
            ChatTaskSource.objects.filter(
                user=self.user,
                service="signal",
                channel_identifier__icontains="template_profiler.js",
            ).exists()
        )

    def test_tasks_hub_shows_human_creator_label(self):
        project = TaskProject.objects.create(user=self.user, name="Creator Test")
        session = ChatSession.objects.create(user=self.user, identifier=self.pid_signal)
        origin = Message.objects.create(
            user=self.user,
            session=session,
            ts=1_700_000_000_000,
            text="task: write docs",
            sender_uuid="+15551230000",
            custom_author="OTHER",
            source_service="signal",
            source_chat_id="+15551230000",
        )
        DerivedTask.objects.create(
            user=self.user,
            project=project,
            title="Write docs",
            source_service="signal",
            source_channel="+15551230000",
            origin_message=origin,
            reference_code="1",
            status_snapshot="open",
        )

        response = self.client.get(reverse("tasks_hub"))
        self.assertEqual(200, response.status_code)
        self.assertContains(response, "Scope Person")

    def test_tasks_hub_can_create_manual_task_without_chat_source(self):
        project = TaskProject.objects.create(user=self.user, name="Manual Project")
        response = self.client.post(
            reverse("tasks_hub"),
            {
                "action": "task_create",
                "project_id": str(project.id),
                "title": "Manual web task",
                "due_date": "2026-03-10",
                "assignee_identifier": "@operator",
            },
            follow=True,
        )
        self.assertEqual(200, response.status_code)
        task = DerivedTask.objects.get(user=self.user, project=project, title="Manual web task")
        self.assertEqual("web", task.source_service)
        self.assertEqual("@operator", task.assignee_identifier)
        self.assertEqual("2026-03-10", task.due_date.isoformat())
        event = task.events.order_by("-created_at").first()
        self.assertEqual("created", event.event_type)
        self.assertEqual("web_ui", str((event.payload or {}).get("via") or ""))

    def test_project_page_creator_column_links_to_compose(self):
        project = TaskProject.objects.create(user=self.user, name="Creator Link Test")
        session = ChatSession.objects.create(user=self.user, identifier=self.pid_signal)
        origin = Message.objects.create(
            user=self.user,
            session=session,
            ts=1_700_000_000_111,
            text="task: creator link",
            sender_uuid="+15551230000",
            custom_author="OTHER",
            source_service="signal",
            source_chat_id="+15551230000",
        )
        DerivedTask.objects.create(
            user=self.user,
            project=project,
            title="Creator link task",
            source_service="signal",
            source_channel="+15551230000",
            origin_message=origin,
            reference_code="2",
            status_snapshot="open",
        )
        response = self.client.get(
            reverse("tasks_project", kwargs={"project_id": str(project.id)})
        )
        self.assertEqual(200, response.status_code)
        self.assertContains(
            response,
            f'{reverse("compose_page")}?service=signal&amp;identifier=%2B15551230000&amp;person={self.person.id}',
        )

    def test_task_detail_renders_payload_summary_and_json(self):
        project = TaskProject.objects.create(user=self.user, name="Payload Test")
        session = ChatSession.objects.create(user=self.user, identifier=self.pid_signal)
        origin = Message.objects.create(
            user=self.user,
            session=session,
            ts=1_700_000_000_000,
            text="origin",
            sender_uuid="+15551230000",
            custom_author="OTHER",
            source_service="signal",
            source_chat_id="+15551230000",
        )
        task = DerivedTask.objects.create(
            user=self.user,
            project=project,
            title="Payload detail",
            source_service="signal",
            source_channel="+15551230000",
            origin_message=origin,
            reference_code="42",
            status_snapshot="open",
        )
        DerivedTaskEvent.objects.create(
            task=task,
            event_type="reaction_captured",
            payload={"source": "signal", "emoji": "❤️", "reason": "heart_reaction"},
        )

        response = self.client.get(
            reverse("tasks_task", kwargs={"task_id": str(task.id)})
        )
        self.assertEqual(200, response.status_code)
        self.assertContains(response, "View payload JSON")
        self.assertContains(response, "<strong>source</strong>: signal", html=True)
        self.assertContains(response, "&quot;emoji&quot;: &quot;❤️&quot;")
