import json
from types import SimpleNamespace
from unittest.mock import patch

from django.contrib.auth import get_user_model
from django.http import Http404
from django.test import RequestFactory, TestCase
from ..constants import (
    DISPLAY_MODE_WIDGET,
    MAX_MESSAGE_LENGTH,
    MODE_ADMIN,
    MODE_CUSTOMER,
    ROLE_ASSISTANT,
    ROLE_USER,
    SESSION_CLOSED,
)
from ..models import AiAssistantMessage, AiAssistantSession
from ..views import _chat_response, _reset_response, admin_chat_page, customer_chat_page
from .utils import make_settings

User = get_user_model()


class AiAssistantViewTests(TestCase):
    def setUp(self):
        self.factory = RequestFactory()
        self.site = SimpleNamespace(id=77)
        self.staff_user = User.objects.create_user(
            username="staff",
            password="test-pass",
            is_staff=True,
        )
        self.other_staff_user = User.objects.create_user(
            username="other-staff",
            password="test-pass",
            is_staff=True,
        )
        self.customer_settings = make_settings()

    def _request(self, payload, user=None, cookies=None):
        request = self.factory.post(
            "/ai-assistant/api/chat/",
            data=json.dumps(payload),
            content_type="application/json",
        )
        request.user = user or SimpleNamespace(is_authenticated=False, is_staff=False)
        request.COOKIES = cookies or {}
        return request

    def _call_chat(self, request, mode=MODE_CUSTOMER, require_staff=False):
        with patch(
            "ocyan.plugin.ai_assistant.views._current_site", return_value=self.site
        ), patch(
            "ocyan.plugin.ai_assistant.views._site_settings",
            return_value=self.customer_settings,
        ):
            return _chat_response(request, mode, require_staff=require_staff)

    def _call_reset(self, request, mode=MODE_CUSTOMER, require_staff=False):
        with patch(
            "ocyan.plugin.ai_assistant.views._current_site", return_value=self.site
        ), patch(
            "ocyan.plugin.ai_assistant.views._site_settings",
            return_value=self.customer_settings,
        ):
            return _reset_response(request, mode, require_staff=require_staff)

    def test_invalid_json_is_rejected_cleanly(self):
        request = self.factory.post(
            "/ai-assistant/api/chat/",
            data="{not-json",
            content_type="application/json",
        )
        request.user = SimpleNamespace(is_authenticated=False, is_staff=False)
        request.COOKIES = {}
        response = self._call_chat(request)
        self.assertEqual(response.status_code, 400)
        self.assertEqual(json.loads(response.content), {"error": "invalid_json"})

    def test_empty_message_is_rejected(self):
        response = self._call_chat(self._request({"message": "   "}))
        self.assertEqual(response.status_code, 400)
        self.assertEqual(json.loads(response.content), {"error": "message_required"})

    def test_message_too_long_is_rejected(self):
        response = self._call_chat(
            self._request({"message": "x" * (MAX_MESSAGE_LENGTH + 1)})
        )
        self.assertEqual(response.status_code, 400)
        payload = json.loads(response.content)
        self.assertEqual(payload["error"], "message_too_long")
        self.assertEqual(payload["max_length"], MAX_MESSAGE_LENGTH)

    def test_current_user_message_is_not_duplicated_in_context(self):
        session = AiAssistantSession.objects.create(
            site_id=self.site.id,
            mode=MODE_CUSTOMER,
            visitor_id="visitor-1",
        )
        AiAssistantMessage.objects.create(
            session=session, role=ROLE_USER, content="Earlier"
        )
        captured = {}

        def fake_generate_reply(
            settings_obj,
            mode,
            context_messages,
            user_message,
            request=None,
            capability_context=None,
        ):
            captured["context"] = [(m.role, m.content) for m in context_messages]
            captured["user_message"] = user_message
            return "Assistant reply", {"provider": "fake"}

        request = self._request(
            {"message": "New question", "session_id": str(session.uuid)},
            cookies={"mb_ai_vid": "visitor-1"},
        )
        with patch(
            "ocyan.plugin.ai_assistant.views.generate_reply",
            side_effect=fake_generate_reply,
        ):
            response = self._call_chat(request)

        self.assertEqual(response.status_code, 200)
        self.assertEqual(captured["context"], [(ROLE_USER, "Earlier")])
        self.assertEqual(captured["user_message"], "New question")
        self.assertEqual(
            list(session.messages.values_list("role", "content")),
            [
                (ROLE_USER, "Earlier"),
                (ROLE_USER, "New question"),
                (ROLE_ASSISTANT, "Assistant reply"),
            ],
        )

    def test_customer_session_is_tied_to_visitor_cookie(self):
        session = AiAssistantSession.objects.create(
            site_id=self.site.id,
            mode=MODE_CUSTOMER,
            visitor_id="visitor-1",
        )
        request = self._request(
            {"message": "Hello", "session_id": str(session.uuid)},
            cookies={"mb_ai_vid": "visitor-2"},
        )
        with patch(
            "ocyan.plugin.ai_assistant.views.generate_reply",
            return_value=("Reply", {"provider": "fake"}),
        ):
            response = self._call_chat(request)

        payload = json.loads(response.content)
        self.assertNotEqual(payload["session_id"], str(session.uuid))

    def test_admin_session_is_tied_to_staff_user(self):
        session = AiAssistantSession.objects.create(
            site_id=self.site.id,
            mode=MODE_ADMIN,
            user=self.other_staff_user,
        )
        request = self._request(
            {"message": "Hello", "session_id": str(session.uuid)},
            user=self.staff_user,
        )
        with patch(
            "ocyan.plugin.ai_assistant.views.generate_reply",
            return_value=("Reply", {"provider": "fake"}),
        ):
            response = self._call_chat(request, mode=MODE_ADMIN, require_staff=True)

        payload = json.loads(response.content)
        self.assertNotEqual(payload["session_id"], str(session.uuid))

    def test_admin_requires_staff_when_flag_enabled(self):
        request = self._request(
            {"message": "Hello"},
            user=SimpleNamespace(is_authenticated=True, is_staff=False),
        )
        response = self._call_chat(request, mode=MODE_ADMIN, require_staff=True)
        self.assertEqual(response.status_code, 403)
        self.assertEqual(
            json.loads(response.content), {"error": "admin_assistant_disabled"}
        )

    def test_customer_endpoint_never_activates_business_scope(self):
        captured = {}

        def fake_generate_reply(
            settings_obj,
            mode,
            context_messages,
            user_message,
            request=None,
            capability_context=None,
        ):
            captured["capability_context"] = capability_context
            return "Reply", {"provider": "fake"}

        request = self._request({"message": "Hello", "business_mode": True})
        with patch(
            "ocyan.plugin.ai_assistant.views.generate_reply",
            side_effect=fake_generate_reply,
        ):
            response = self._call_chat(request, mode=MODE_CUSTOMER, require_staff=False)

        self.assertEqual(response.status_code, 200)
        self.assertFalse(captured["capability_context"].business_mode_requested)
        self.assertFalse(captured["capability_context"].business_skills_allowed)

    def test_admin_endpoint_does_not_activate_business_scope_without_request(self):
        captured = {}

        def fake_generate_reply(
            settings_obj,
            mode,
            context_messages,
            user_message,
            request=None,
            capability_context=None,
        ):
            captured["capability_context"] = capability_context
            return "Reply", {"provider": "fake"}

        request = self._request({"message": "Hello"}, user=self.staff_user)
        with patch(
            "ocyan.plugin.ai_assistant.views.generate_reply",
            side_effect=fake_generate_reply,
        ):
            response = self._call_chat(request, mode=MODE_ADMIN, require_staff=True)

        self.assertEqual(response.status_code, 200)
        self.assertFalse(captured["capability_context"].business_mode_requested)
        self.assertFalse(captured["capability_context"].business_skills_allowed)

    def test_admin_endpoint_does_not_activate_business_scope_when_disabled(self):
        captured = {}
        self.customer_settings.business_assistant_enabled = False

        def fake_generate_reply(
            settings_obj,
            mode,
            context_messages,
            user_message,
            request=None,
            capability_context=None,
        ):
            captured["capability_context"] = capability_context
            return "Reply", {"provider": "fake"}

        request = self._request(
            {"message": "Hello", "business_mode": True}, user=self.staff_user
        )
        with patch(
            "ocyan.plugin.ai_assistant.views.generate_reply",
            side_effect=fake_generate_reply,
        ):
            response = self._call_chat(request, mode=MODE_ADMIN, require_staff=True)

        self.assertEqual(response.status_code, 200)
        self.assertTrue(captured["capability_context"].business_mode_requested)
        self.assertFalse(captured["capability_context"].business_skills_allowed)

    def test_admin_endpoint_activates_business_scope_only_when_enabled_and_requested(
        self,
    ):
        captured = {}
        self.customer_settings.business_assistant_enabled = True
        self.customer_settings.business_assistant_requires_staff = True

        def fake_generate_reply(
            settings_obj,
            mode,
            context_messages,
            user_message,
            request=None,
            capability_context=None,
        ):
            captured["capability_context"] = capability_context
            return "Reply", {"provider": "fake"}

        request = self._request(
            {"message": "Hello", "business_mode": True}, user=self.staff_user
        )
        with patch(
            "ocyan.plugin.ai_assistant.views.generate_reply",
            side_effect=fake_generate_reply,
        ):
            response = self._call_chat(request, mode=MODE_ADMIN, require_staff=True)

        self.assertEqual(response.status_code, 200)
        self.assertTrue(captured["capability_context"].business_mode_requested)
        self.assertTrue(captured["capability_context"].business_mode_enabled)
        self.assertTrue(captured["capability_context"].staff_allowed)
        self.assertTrue(captured["capability_context"].business_skills_allowed)

    def test_admin_business_scope_still_rejected_for_non_staff(self):
        request = self._request(
            {"message": "Hello", "business_mode": True},
            user=SimpleNamespace(is_authenticated=True, is_staff=False),
        )
        response = self._call_chat(request, mode=MODE_ADMIN, require_staff=True)
        self.assertEqual(response.status_code, 403)
        self.assertEqual(
            json.loads(response.content), {"error": "admin_assistant_disabled"}
        )

    def test_customer_chat_page_does_not_render_business_toggle(self):
        request = self.factory.get("/ai-assistant/")
        request.user = SimpleNamespace(is_authenticated=False, is_staff=False)
        captured = {}

        def fake_render(request, template_name, context):
            captured["template_name"] = template_name
            captured["context"] = context
            return SimpleNamespace(content=b"ok")

        with patch(
            "ocyan.plugin.ai_assistant.views._current_site", return_value=self.site
        ), patch(
            "ocyan.plugin.ai_assistant.views._site_settings",
            return_value=self.customer_settings,
        ), patch(
            "ocyan.plugin.ai_assistant.views.render",
            side_effect=fake_render,
        ):
            customer_chat_page(request)

        self.assertEqual(captured["template_name"], "ai_assistant/customer_chat.html")
        self.assertNotIn("business_mode_available", captured["context"])
        self.assertNotIn("business_mode_label", captured["context"])

    def test_admin_chat_page_renders_business_toggle_only_when_enabled(self):
        request = self.factory.get("/admin/ai-assistant/")
        request.user = self.staff_user
        self.customer_settings.business_assistant_enabled = True
        captured = {}

        def fake_render(request, template_name, context):
            captured["template_name"] = template_name
            captured["context"] = context
            return SimpleNamespace(content=b"ok")

        with patch(
            "ocyan.plugin.ai_assistant.views._current_site", return_value=self.site
        ), patch(
            "ocyan.plugin.ai_assistant.views._site_settings",
            return_value=self.customer_settings,
        ), patch(
            "ocyan.plugin.ai_assistant.views.reverse",
            side_effect=lambda name: f"/mocked/{name}/",
        ), patch(
            "ocyan.plugin.ai_assistant.views.render",
            side_effect=fake_render,
        ):
            admin_chat_page(request)

        self.assertEqual(
            captured["template_name"], "wagtailadmin/ai_assistant/dashboard.html"
        )
        self.assertTrue(captured["context"]["business_mode_available"])
        self.assertEqual(
            captured["context"]["business_mode_label"], "Business Assistant"
        )

    def test_admin_chat_page_hides_business_toggle_when_disabled(self):
        request = self.factory.get("/admin/ai-assistant/")
        request.user = self.staff_user
        self.customer_settings.business_assistant_enabled = False
        captured = {}

        def fake_render(request, template_name, context):
            captured["template_name"] = template_name
            captured["context"] = context
            return SimpleNamespace(content=b"ok")

        with patch(
            "ocyan.plugin.ai_assistant.views._current_site", return_value=self.site
        ), patch(
            "ocyan.plugin.ai_assistant.views._site_settings",
            return_value=self.customer_settings,
        ), patch(
            "ocyan.plugin.ai_assistant.views.reverse",
            side_effect=lambda name: f"/mocked/{name}/",
        ), patch(
            "ocyan.plugin.ai_assistant.views.render",
            side_effect=fake_render,
        ):
            admin_chat_page(request)

        self.assertEqual(
            captured["template_name"], "wagtailadmin/ai_assistant/dashboard.html"
        )
        self.assertFalse(captured["context"]["business_mode_available"])

    def test_customer_reset_closes_owned_session(self):
        session = AiAssistantSession.objects.create(
            site_id=self.site.id,
            mode=MODE_CUSTOMER,
            visitor_id="visitor-1",
        )
        request = self._request(
            {"session_id": str(session.uuid)},
            cookies={"mb_ai_vid": "visitor-1"},
        )

        response = self._call_reset(request)

        self.assertEqual(response.status_code, 200)
        self.assertEqual(json.loads(response.content), {"cleared": True})
        session.refresh_from_db()
        self.assertEqual(session.status, SESSION_CLOSED)

    def test_customer_reset_does_not_close_mismatched_session(self):
        session = AiAssistantSession.objects.create(
            site_id=self.site.id,
            mode=MODE_CUSTOMER,
            visitor_id="visitor-1",
        )
        request = self._request(
            {"session_id": str(session.uuid)},
            cookies={"mb_ai_vid": "visitor-2"},
        )

        response = self._call_reset(request)

        self.assertEqual(response.status_code, 200)
        self.assertEqual(json.loads(response.content), {"cleared": True})
        session.refresh_from_db()
        self.assertEqual(session.status, "open")

    def test_admin_reset_closes_owned_staff_session(self):
        session = AiAssistantSession.objects.create(
            site_id=self.site.id,
            mode=MODE_ADMIN,
            user=self.staff_user,
        )
        request = self._request(
            {"session_id": str(session.uuid)},
            user=self.staff_user,
        )

        response = self._call_reset(request, mode=MODE_ADMIN, require_staff=True)

        self.assertEqual(response.status_code, 200)
        self.assertEqual(json.loads(response.content), {"cleared": True})
        session.refresh_from_db()
        self.assertEqual(session.status, SESSION_CLOSED)

    def test_admin_reset_does_not_close_other_staff_session(self):
        session = AiAssistantSession.objects.create(
            site_id=self.site.id,
            mode=MODE_ADMIN,
            user=self.other_staff_user,
        )
        request = self._request(
            {"session_id": str(session.uuid)},
            user=self.staff_user,
        )

        response = self._call_reset(request, mode=MODE_ADMIN, require_staff=True)

        self.assertEqual(response.status_code, 200)
        self.assertEqual(json.loads(response.content), {"cleared": True})
        session.refresh_from_db()
        self.assertEqual(session.status, "open")

    def test_admin_reset_requires_staff(self):
        request = self._request(
            {"session_id": "00000000-0000-0000-0000-000000000000"},
            user=SimpleNamespace(is_authenticated=True, is_staff=False),
        )

        response = self._call_reset(request, mode=MODE_ADMIN, require_staff=True)

        self.assertEqual(response.status_code, 403)
        self.assertEqual(
            json.loads(response.content), {"error": "admin_assistant_disabled"}
        )

    def test_customer_chat_page_requires_page_mode(self):
        request = self.factory.get("/ai-assistant/")
        request.user = SimpleNamespace(is_authenticated=False, is_staff=False)
        settings_obj = make_settings(customer_display_mode=DISPLAY_MODE_WIDGET)

        with patch(
            "ocyan.plugin.ai_assistant.views._current_site", return_value=self.site
        ), patch(
            "ocyan.plugin.ai_assistant.views._site_settings", return_value=settings_obj
        ):
            with self.assertRaises(Http404):
                customer_chat_page(request)
