from unittest.mock import patch

from django.contrib.auth.models import User
from django.test import Client, TestCase, override_settings
from django.urls import reverse

from company_site.settings.base import build_csrf_trusted_origins

from .models import Contact, EmailMessage, PageVisit
from .seo import SERVICE_URL_NAMES, get_service_entries
from .traffic import TrafficType, classify_user_agent


class CsrfTrustedOriginsTests(TestCase):
    def test_derives_https_origins_from_public_hosts(self):
        origins = build_csrf_trusted_origins(
            ["aimloperations.com", "www.aimloperations.com"]
        )

        self.assertEqual(
            origins,
            [
                "https://aimloperations.com",
                "https://www.aimloperations.com",
            ],
        )

    def test_derives_http_origins_for_local_hosts(self):
        origins = build_csrf_trusted_origins(["localhost", "127.0.0.1"])

        self.assertEqual(origins, ["http://localhost", "http://127.0.0.1"])

    def test_explicit_origins_win(self):
        origins = build_csrf_trusted_origins(
            ["aimloperations.com"],
            ["https://custom.example"],
        )

        self.assertEqual(origins, ["https://custom.example"])


class LogoutCsrfTests(TestCase):
    def setUp(self):
        self.client = Client(enforce_csrf_checks=True)
        self.user = User.objects.create_user(username="logout_user", password="pass")

    def test_logout_post_with_csrf_succeeds(self):
        self.client.login(username="logout_user", password="pass")
        self.client.get("/")
        csrf = self.client.cookies["csrftoken"].value

        response = self.client.post(
            reverse("logout"),
            {"csrfmiddlewaretoken": csrf},
        )

        self.assertEqual(response.status_code, 302)
        self.assertEqual(response.url, "/")
        self.assertNotIn("_auth_user_id", self.client.session)


class PreviewEmailAuthTests(TestCase):
    def setUp(self):
        self.client = Client()
        self.user = User.objects.create_user(username="previewer", password="pass")
        self.email = EmailMessage.objects.create(
            subject="Preview subject",
            body="Preview body content",
            recipient="recipient@example.com",
        )
        self.url = reverse("preview_email", kwargs={"pk": self.email.pk})

    def test_unauthenticated_user_is_redirected_to_login(self):
        response = self.client.get(self.url)

        self.assertEqual(response.status_code, 302)
        self.assertIn("/accounts/login/", response.url)

    def test_authenticated_user_can_preview_email(self):
        self.client.login(username="previewer", password="pass")

        response = self.client.get(self.url)

        self.assertEqual(response.status_code, 200)
        self.assertContains(response, "Preview subject")
        self.assertContains(response, "Preview body content")


@override_settings(
    DEBUG=True,
    EMAIL_BACKEND="django.core.mail.backends.locmem.EmailBackend",
)
class ContactViewTests(TestCase):
    def setUp(self):
        self.client = Client()
        self.url = reverse("contact")
        self.valid_payload = {
            "name": "Jane Doe",
            "email": "jane@example.com",
            "subject": "Automation inquiry",
            "message": "We need help automating our workflow.",
        }

    def test_contact_get_renders_form(self):
        response = self.client.get(self.url)

        self.assertEqual(response.status_code, 200)
        self.assertContains(response, "Send Us a Message")
        self.assertContains(response, 'id="contact-name"')
        self.assertContains(response, 'for="contact-name"')
        self.assertContains(response, 'id="contact-email"')
        self.assertContains(response, 'for="contact-email"')
        self.assertContains(response, 'id="contact-subject"')
        self.assertContains(response, 'for="contact-subject"')
        self.assertContains(response, 'id="contact-message"')
        self.assertContains(response, 'for="contact-message"')

    @patch("public.views.send_contact_email")
    def test_contact_post_success_saves_contact_and_sends_email(self, mock_send_email):
        response = self.client.post(self.url, self.valid_payload)

        self.assertEqual(response.status_code, 200)
        self.assertContains(response, "We'll be in contact shortly!")

        contact = Contact.objects.get()
        self.assertEqual(contact.name, "Jane Doe")
        self.assertEqual(contact.email, "jane@example.com")
        self.assertEqual(contact.subject, "Automation inquiry")
        self.assertEqual(contact.blurb, "We need help automating our workflow.")
        self.assertFalse(contact.contacted)

        mock_send_email.assert_called_once_with(
            "jane@example.com",
            "Automation inquiry",
            "We need help automating our workflow.",
        )

    @patch("public.views.send_contact_email")
    def test_contact_post_without_message_still_succeeds(self, mock_send_email):
        payload = {**self.valid_payload, "message": ""}

        response = self.client.post(self.url, payload)

        self.assertEqual(response.status_code, 200)
        self.assertContains(response, "We'll be in contact shortly!")
        self.assertEqual(Contact.objects.count(), 1)
        self.assertEqual(Contact.objects.get().blurb, "")
        mock_send_email.assert_called_once()

    def test_contact_post_missing_name_shows_error(self):
        payload = {**self.valid_payload, "name": ""}

        response = self.client.post(self.url, payload)

        self.assertEqual(response.status_code, 200)
        self.assertContains(response, "Name is required")
        self.assertEqual(Contact.objects.count(), 0)

    def test_contact_post_missing_email_shows_error(self):
        payload = {**self.valid_payload, "email": ""}

        response = self.client.post(self.url, payload)

        self.assertEqual(response.status_code, 200)
        self.assertContains(response, "Email is required")
        self.assertEqual(Contact.objects.count(), 0)

    def test_contact_post_missing_name_and_email_shows_error(self):
        payload = {**self.valid_payload, "name": "", "email": ""}

        response = self.client.post(self.url, payload)

        self.assertEqual(response.status_code, 200)
        self.assertContains(response, "Both name and email are required")
        self.assertEqual(Contact.objects.count(), 0)

    def test_contact_post_missing_subject_does_not_save(self):
        payload = {**self.valid_payload, "subject": ""}

        response = self.client.post(self.url, payload)

        self.assertEqual(response.status_code, 200)
        self.assertNotContains(response, "We'll be in contact shortly!")
        self.assertEqual(Contact.objects.count(), 0)

    @override_settings(DEBUG=False)
    @patch("public.views.FormWithCaptcha.is_valid", return_value=False)
    def test_contact_post_invalid_captcha_shows_error(self, _mock_is_valid):
        response = self.client.post(self.url, self.valid_payload)

        self.assertEqual(response.status_code, 200)
        self.assertContains(response, "There was an error submitting. Try again")
        self.assertEqual(Contact.objects.count(), 0)


@override_settings(DEBUG=True, TIANJI_ENABLED=True)
class TianjiTrackingTests(TestCase):
    def test_homepage_includes_analytics_notice_when_enabled(self):
        response = self.client.get(reverse("public_index"))

        self.assertEqual(response.status_code, 200)
        self.assertContains(response, "cookie-consent-banner")
        self.assertContains(response, "cookie-consent-acknowledge")
        self.assertContains(response, "Acknowledge")
        self.assertNotContains(response, "cookie-consent-decline")
        self.assertNotContains(response, "Accept Analytics")
        self.assertContains(response, "tianji-config")
        self.assertContains(response, "tianji-consent.js")

    @override_settings(TIANJI_ENABLED=False)
    def test_homepage_omits_analytics_notice_when_disabled(self):
        response = self.client.get(reverse("public_index"))

        self.assertEqual(response.status_code, 200)
        self.assertNotContains(response, "cookie-consent-banner")
        self.assertNotContains(response, "tianji-config")


class AgenticBrowsingSeoTests(TestCase):
    def test_robots_txt_is_available(self):
        response = self.client.get(reverse("robots_txt"))

        self.assertEqual(response.status_code, 200)
        self.assertEqual(response["Content-Type"], "text/plain; charset=utf-8")
        self.assertContains(response, "User-agent: *")
        self.assertContains(response, "Sitemap:")
        self.assertContains(response, "llms.txt")

    def test_sitemap_xml_lists_public_pages(self):
        response = self.client.get(reverse("sitemap_xml"))

        self.assertEqual(response.status_code, 200)
        self.assertEqual(response["Content-Type"], "application/xml; charset=utf-8")
        self.assertContains(response, "<urlset")
        self.assertContains(response, reverse("contact"))
        self.assertContains(response, reverse("forward_deployed"))

    def test_llms_txt_is_available(self):
        response = self.client.get(reverse("llms_txt"))

        self.assertEqual(response.status_code, 200)
        self.assertEqual(response["Content-Type"], "text/plain; charset=utf-8")
        self.assertContains(response, "# AI ML Operations, LLC")
        self.assertContains(response, "## Key pages")
        self.assertContains(response, reverse("contact"))
        self.assertContains(response, "## Web design & hosting")
        self.assertContains(response, "estimate_web_design_cost")
        self.assertContains(response, "email_sms")
        self.assertContains(response, "three instances")
        self.assertContains(response, "Tailored to your brand")
        self.assertContains(response, "You own the site")
        self.assertContains(response, "Grafana metrics")
        self.assertContains(response, "brand-tailored")

    def test_homepage_uses_semantic_nav_controls(self):
        response = self.client.get(reverse("public_index"))

        self.assertEqual(response.status_code, 200)
        self.assertContains(response, 'id="services-menu-button"')
        self.assertContains(response, 'aria-haspopup="true"')
        self.assertContains(response, 'aria-label="Main navigation"')


@override_settings(DEBUG=True, WEBMCP_ENABLED=True)
class WebMcpTests(TestCase):
    def test_homepage_includes_webmcp_when_enabled(self):
        response = self.client.get(reverse("public_index"))

        self.assertEqual(response.status_code, 200)
        self.assertContains(response, "webmcp-config")
        self.assertContains(response, "webmcp-tools.js")
        self.assertContains(response, 'data-page-name="public_index"')
        self.assertContains(response, "forward_deployed")

    @override_settings(WEBMCP_ENABLED=False)
    def test_homepage_omits_webmcp_when_disabled(self):
        response = self.client.get(reverse("public_index"))

        self.assertEqual(response.status_code, 200)
        self.assertNotContains(response, "webmcp-config")
        self.assertNotContains(response, "webmcp-tools.js")

    def test_contact_page_includes_webmcp_form_annotations(self):
        response = self.client.get(reverse("contact"))

        self.assertEqual(response.status_code, 200)
        self.assertContains(response, 'id="contact-form"')
        self.assertContains(response, 'toolname="submit_contact_inquiry"')
        self.assertContains(response, "tooldescription=")
        self.assertContains(response, "toolparamdescription=")
        self.assertContains(response, "submit_contact_inquiry")

    @override_settings(DEBUG=False)
    def test_contact_page_renders_captcha_outside_annotated_form(self):
        response = self.client.get(reverse("contact"))

        self.assertEqual(response.status_code, 200)
        form_end = response.content.index(b"</form>")
        captcha_index = response.content.index(b"g-recaptcha")
        self.assertGreater(captcha_index, form_end)
        self.assertContains(response, b'form="contact-form"')

    def test_webmcp_services_json_includes_all_service_pages(self):
        response = self.client.get(reverse("public_index"))

        self.assertEqual(response.status_code, 200)
        for slug in SERVICE_URL_NAMES:
            self.assertContains(response, slug)

    def test_service_entries_match_public_seo_config(self):
        entries = get_service_entries()
        self.assertEqual(len(entries), len(SERVICE_URL_NAMES))
        self.assertEqual({entry["slug"] for entry in entries}, SERVICE_URL_NAMES)

    def test_webmcp_static_script_defines_expected_tools(self):
        from pathlib import Path

        script_path = Path(__file__).resolve().parent / "static/public/js/webmcp-tools.js"
        script = script_path.read_text(encoding="utf-8")

        for tool_name in (
            "list_services",
            "get_page_content",
            "navigate_to_service",
            "open_contact_with_subject",
            "estimate_web_design_cost",
            "submit_contact_inquiry",
        ):
            self.assertIn("name: '" + tool_name + "'", script)

        self.assertIn("readOnlyHint: true", script)
        self.assertIn("readOnlyHint: false", script)
        self.assertIn("navigator.modelContext || document.modelContext", script)
        self.assertIn("webDesignPricing", script)

    def test_webmcp_config_includes_web_design_pricing(self):
        response = self.client.get(reverse("public_index"))

        self.assertEqual(response.status_code, 200)
        self.assertContains(response, "data-web-design-pricing=")
        self.assertContains(response, "email_sms")
        self.assertContains(response, "client_portal")


class WebDesignPricingTests(TestCase):
    def test_web_design_page_renders_estimator(self):
        response = self.client.get(reverse("web_design"))

        self.assertEqual(response.status_code, 200)
        self.assertContains(response, "Build Your Package")
        self.assertContains(response, "pricingEstimator")
        self.assertContains(response, "Public site")
        self.assertContains(response, "Client portal + UTM")
        self.assertContains(response, "three instances")
        self.assertContains(response, "UTM tracking")
        self.assertContains(response, "Grafana metrics &amp; alerts")
        self.assertContains(response, "You own the site")
        self.assertContains(response, "Tailored to your brand")
        self.assertContains(response, reverse("contact"))
        self.assertContains(response, 'data-tianji-event="web_design_estimate_submit"')
        self.assertContains(response, 'data-tianji-event="web_design_contact_cta"')
        self.assertContains(response, "web_design_estimate_toggle")
        self.assertContains(response, "aimlTrackWhenReady")
        self.assertContains(response, "data-tianji-event-build=")
        self.assertContains(response, "data-tianji-event-features=")

    def test_base_estimate_includes_required_features_only(self):
        from .web_design_pricing import estimate_web_design_cost

        estimate = estimate_web_design_cost()
        self.assertEqual(estimate["one_time_build"], 600)
        self.assertEqual(estimate["monthly"], 40)
        self.assertEqual(estimate["selected_count"], 2)

    def test_payments_auto_selects_email_sms(self):
        from .web_design_pricing import estimate_web_design_cost

        estimate = estimate_web_design_cost(["payments"])
        selected_ids = {item["id"] for item in estimate["selected"]}
        self.assertIn("email_sms", selected_ids)
        self.assertIn("payments", selected_ids)
        self.assertEqual(estimate["one_time_build"], 1600)
        self.assertEqual(estimate["monthly"], 70)

    def test_ai_social_auto_selects_social(self):
        from .web_design_pricing import estimate_web_design_cost

        estimate = estimate_web_design_cost(["ai_social"])
        selected_ids = {item["id"] for item in estimate["selected"]}
        self.assertIn("social", selected_ids)
        self.assertIn("ai_social", selected_ids)
        self.assertEqual(estimate["one_time_build"], 1400)
        self.assertEqual(estimate["monthly"], 80)


class TrafficClassificationTests(TestCase):
    def test_classifies_common_agents(self):
        cases = [
            ("Mozilla/5.0 (Macintosh) Chrome/120.0.0.0 Safari/537.36", TrafficType.HUMAN),
            ("Mozilla/5.0 (compatible; Googlebot/2.1; +http://www.google.com/bot.html)", TrafficType.SEARCH_INDEXER),
            ("Mozilla/5.0 AppleWebKit/537.36 (KHTML, like Gecko; compatible; GPTBot/1.0)", TrafficType.AI_BOT),
            ("ClaudeBot/1.0", TrafficType.AI_BOT),
            ("facebookexternalhit/1.1", TrafficType.SOCIAL_BOT),
            ("UptimeRobot/2.0", TrafficType.MONITORING),
            ("python-requests/2.31.0", TrafficType.OTHER_BOT),
            ("", TrafficType.UNKNOWN),
        ]
        for ua, expected in cases:
            with self.subTest(ua=ua):
                self.assertEqual(classify_user_agent(ua), expected)


class UTMTrackingTests(TestCase):
    def setUp(self):
        self.client = Client(
            HTTP_USER_AGENT="Mozilla/5.0 (Macintosh) Chrome/120.0.0.0 Safari/537.36"
        )
        self.staff = User.objects.create_user(
            username="staff_utm", password="pass", is_staff=True
        )
        self.regular = User.objects.create_user(username="plain_utm", password="pass")

    def test_page_visit_records_utm_and_human_type(self):
        response = self.client.get(
            "/?utm_source=linkedin&utm_medium=social&utm_campaign=spring"
        )
        self.assertEqual(response.status_code, 200)
        visit = PageVisit.objects.latest("created")
        self.assertEqual(visit.path, "/")
        self.assertEqual(visit.utm_source, "linkedin")
        self.assertEqual(visit.utm_medium, "social")
        self.assertEqual(visit.utm_campaign, "spring")
        self.assertTrue(visit.is_landing)
        self.assertEqual(visit.traffic_type, TrafficType.HUMAN)

    def test_utm_persists_on_next_page_via_session(self):
        self.client.get("/?utm_source=newsletter&utm_medium=email&utm_campaign=march")
        self.client.get("/contact")
        visit = PageVisit.objects.filter(path="/contact").latest("created")
        self.assertEqual(visit.utm_source, "newsletter")
        self.assertEqual(visit.utm_campaign, "march")
        self.assertFalse(visit.is_landing)

    def test_dashboard_requires_staff(self):
        url = reverse("utm_dashboard")
        anon = self.client.get(url)
        self.assertEqual(anon.status_code, 302)
        self.assertIn("login", anon.url)

        self.client.login(username="plain_utm", password="pass")
        denied = self.client.get(url)
        self.assertEqual(denied.status_code, 302)

        self.client.login(username="staff_utm", password="pass")
        ok = self.client.get(url)
        self.assertEqual(ok.status_code, 200)
        self.assertContains(ok, "UTM &amp; Traffic Analytics")

    def test_search_bot_classified(self):
        bot_client = Client(
            HTTP_USER_AGENT="Mozilla/5.0 (compatible; Googlebot/2.1; +http://www.google.com/bot.html)"
        )
        bot_client.get("/")
        visit = PageVisit.objects.latest("created")
        self.assertEqual(visit.traffic_type, TrafficType.SEARCH_INDEXER)


class LeadsDashboardTests(TestCase):
    def setUp(self):
        self.client = Client()
        self.staff = User.objects.create_user(
            username="staff_leads", password="pass", is_staff=True
        )
        self.regular = User.objects.create_user(username="plain_leads", password="pass")
        self.lead = Contact.objects.create(
            name="Ada Lovelace",
            email="ada@example.com",
            subject="AI help",
            blurb="Need forward-deployed support.",
            utm_source="linkedin",
            utm_medium="social",
            utm_campaign="spring",
        )

    def test_leads_list_requires_staff(self):
        url = reverse("leads_list")
        self.assertEqual(self.client.get(url).status_code, 302)

        self.client.login(username="plain_leads", password="pass")
        self.assertEqual(self.client.get(url).status_code, 302)

        self.client.login(username="staff_leads", password="pass")
        response = self.client.get(url)
        self.assertEqual(response.status_code, 200)
        self.assertContains(response, "Ada Lovelace")
        self.assertContains(response, "ada@example.com")

    def test_lead_detail_and_toggle(self):
        self.client.login(username="staff_leads", password="pass")
        detail = reverse("lead_detail", kwargs={"pk": self.lead.pk})
        response = self.client.get(detail)
        self.assertEqual(response.status_code, 200)
        self.assertContains(response, "Need forward-deployed support.")
        self.assertContains(response, "linkedin")

        toggle = reverse("lead_toggle_contacted", kwargs={"pk": self.lead.pk})
        response = self.client.post(toggle, {"next": detail})
        self.assertEqual(response.status_code, 302)
        self.lead.refresh_from_db()
        self.assertTrue(self.lead.contacted)
