from django.http import HttpResponse
from django.test import RequestFactory, SimpleTestCase, Client, override_settings

from mainapp.utils.locale_path import (
    alternate_locale_paths,
    collapse_recursive_locale_path,
    ensure_trailing_slash,
    swap_locale_prefix,
)
from mysite.middleware import NormalizeScriptNameMiddleware


class RedirectSourcePathTests(SimpleTestCase):
    def test_arabic_encoded_source_matches_decoded_request(self):
        from types import SimpleNamespace
        from unittest.mock import patch

        from mainapp.utils.redirect_rules import lookup_redirect, source_path_variants

        encoded = "/ar/services/%D8%B3%D8%AE%D8%A7%D9%86%D8%A7%D8%AA"
        decoded = "/ar/services/سخانات/"
        self.assertIn("/ar/services/سخانات", source_path_variants(encoded))
        self.assertIn("/ar/services/سخانات", source_path_variants(decoded))

        rule = SimpleNamespace(source=encoded, status_code=410, is_active=True)
        with patch("mainapp.utils.redirect_rules._load_redirect_map", return_value={
            key: rule for key in source_path_variants(encoded)
        }):
            self.assertIs(lookup_redirect(decoded), rule)
            self.assertIs(lookup_redirect(encoded), rule)


class CollapseRecursiveLocalePathTests(SimpleTestCase):
    def test_collapses_repeated_service_segment(self):
        path = "/ar/services/foo/ar/services/bar/"
        self.assertEqual(collapse_recursive_locale_path(path), "/ar/services/bar/")

    def test_collapses_to_locale_home(self):
        path = "/ar/services/foo/ar/"
        self.assertEqual(collapse_recursive_locale_path(path), "/ar/")

    def test_collapses_multiple_levels(self):
        path = "/ar/services/a/ar/services/b/ar/services/c/"
        self.assertEqual(collapse_recursive_locale_path(path), "/ar/services/c/")

    def test_leaves_valid_paths_unchanged(self):
        for path in (
            "/ar/services/epc-projects/",
            "/en/services/epc-projects/",
            "/ar/services/",
            "/ar/",
        ):
            self.assertIsNone(collapse_recursive_locale_path(path))


class SwapLocalePrefixTests(SimpleTestCase):
    def test_swaps_ar_to_en(self):
        localized = swap_locale_prefix("/ar/about-us/")
        self.assertEqual(localized["en"], "/en/about-us/")
        self.assertEqual(localized["ar"], "/ar/about-us/")


class EnsureTrailingSlashTests(SimpleTestCase):
    def test_adds_slash_to_internal_paths(self):
        self.assertEqual(ensure_trailing_slash("/en/about-us"), "/en/about-us/")
        self.assertEqual(ensure_trailing_slash("/ar"), "/ar/")
        self.assertEqual(ensure_trailing_slash("/en/about-us/"), "/en/about-us/")

    def test_leaves_external_and_anchor_unchanged(self):
        self.assertEqual(ensure_trailing_slash("https://example.com/x"), "https://example.com/x")
        self.assertEqual(ensure_trailing_slash("#"), "#")


class SitePagePathTests(SimpleTestCase):
    def test_home_path(self):
        from mainapp.utils.locale_path import site_page_path

        self.assertEqual(site_page_path("website_home", lang="ar"), "/ar/")
        self.assertEqual(site_page_path("website_about_us", lang="en"), "/en/about-us/")


class ServiceDetailPathBySlugTests(SimpleTestCase):
    def test_builds_path_without_reverse(self):
        from mainapp.utils.service_public import service_detail_path_by_slug

        path = service_detail_path_by_slug("مشاريع-epc", "ar", subpath="")
        self.assertEqual(path, "/ar/services/%D9%85%D8%B4%D8%A7%D8%B1%D9%8A%D8%B9-epc/")


class BlogSlugTests(SimpleTestCase):
    def test_replaces_spaces_with_hyphens_in_blog_path(self):
        from mainapp.utils.blog_public import blog_detail_path_by_slug

        path = blog_detail_path_by_slug("أجهزة التدفئة الحديثة", "ar")
        self.assertNotIn("%20", path)
        self.assertEqual(
            path,
            "/ar/blogs/%D8%A3%D8%AC%D9%87%D8%B2%D8%A9-%D8%A7%D9%84%D8%AA%D8%AF%D9%81%D8%A6%D8%A9-%D8%A7%D9%84%D8%AD%D8%AF%D9%8A%D8%AB%D8%A9/",
        )

    def test_normalizes_unicode_whitespace_runs(self):
        from mainapp.utils.slug_url import normalize_slug_separators

        self.assertEqual(
            normalize_slug_separators("  أجهزة\t التدفئة  الحديثة  "),
            "أجهزة-التدفئة-الحديثة",
        )


class ProductSlugTests(SimpleTestCase):
    def test_replaces_spaces_with_hyphens_in_product_path(self):
        from urllib.parse import unquote

        from mainapp.utils.product_public import product_detail_path_by_slug

        path = product_detail_path_by_slug("سخان مياه أكوا 2012", "ar")
        self.assertNotIn("%20", path)
        self.assertEqual(unquote(path), "/ar/products/سخان-مياه-أكوا-2012/")

    def test_slug_converter_normalizes_spaces(self):
        from mainapp.url_converters import ServiceSlugConverter

        conv = ServiceSlugConverter()
        self.assertEqual(conv.to_url("سخان مياه أكوا 2012"), "سخان-مياه-أكوا-2012")
        self.assertEqual(conv.to_python("سخان%20مياه%20أكوا%202012"), "سخان-مياه-أكوا-2012")


class CategorySlugTests(SimpleTestCase):
    def test_replaces_spaces_with_hyphens_in_category_path(self):
        from urllib.parse import unquote

        from mainapp.utils.category_public import category_detail_path_by_slug

        path = category_detail_path_by_slug("Combi boilers", "en")
        self.assertNotIn("%20", path)
        self.assertEqual(unquote(path), "/en/products/category/Combi-boilers/")

    def test_slug_lookup_matches_hyphen_and_space_variants(self):
        from types import SimpleNamespace
        from unittest.mock import patch

        from mainapp.utils.category_public import get_category_by_slug

        row = SimpleNamespace(
            slug={"en": "Combi boilers", "ar": "غلايات-كومبي"},
            status=True,
        )
        with patch(
            "mainapp.utils.category_public.Categories.objects.exclude",
        ) as exclude_mock:
            exclude_mock.return_value = [row]
            found = get_category_by_slug("Combi-boilers")
        self.assertIs(found, row)


class ProductCategoriesTests(SimpleTestCase):
    def test_set_and_read_product_categories(self):
        from unittest.mock import MagicMock, patch

        from mainapp.utils.product_categories import (
            category_ids_for_product,
            set_product_categories,
        )

        saved: list[tuple[int, int]] = []

        class FakePC:
            objects = MagicMock()

        FakePC.objects.filter.return_value.delete = MagicMock()
        FakePC.objects.create = MagicMock(side_effect=lambda **kw: saved.append((kw["product_id"], kw["category_id"])))

        Products = MagicMock()
        Products.objects.filter.return_value.update = MagicMock()

        with patch("mainapp.utils.product_categories._product_categories_model", return_value=FakePC):
            with patch("legacydb.model_defs.catalog.products.Products", Products):
                set_product_categories(5, [3, 7, 3])
        self.assertEqual(saved, [(5, 3), (5, 7)])
        Products.objects.filter.assert_called_with(pk=5)
        Products.objects.filter.return_value.update.assert_called()

        FakePC.objects.filter.return_value.order_by.return_value.values_list.return_value = [3, 7]
        with patch("mainapp.utils.product_categories._product_categories_model", return_value=FakePC):
            self.assertEqual(category_ids_for_product(5), [3, 7])

    def test_product_public_dict_merges_multiple_filter_classes(self):
        from types import SimpleNamespace
        from unittest.mock import patch

        from mainapp.utils.product_public import product_public_dict

        product = SimpleNamespace(
            id=1,
            pk=1,
            category_id=2,
            service_id=None,
            name={"en": "Tank", "ar": "خزان"},
            slug={"en": "tank", "ar": "tank"},
            short_desc={},
            long_desc={},
            meta_title={},
            meta_desc={},
            image="",
            pdf="",
        )
        cat_map = {
            2: SimpleNamespace(id=2, parent_id=None, name={"en": "Cat A", "ar": "أ"}, slug={"en": "cat-a", "ar": "cat-a"}),
            5: SimpleNamespace(id=5, parent_id=None, name={"en": "Cat B", "ar": "ب"}, slug={"en": "cat-b", "ar": "cat-b"}),
        }
        payload = product_public_dict(
            product,
            "en",
            "ar",
            media_url=lambda p: "",
            category_ids=[2, 5],
            categories_map=cat_map,
        )
        self.assertEqual(payload["category_ids"], [2, 5])
        self.assertEqual(payload["category_names"], ["Cat A", "Cat B"])
        self.assertIn("cat-a", payload["filter_classes"])
        self.assertIn("cat-b", payload["filter_classes"])

    def test_subcategory_uses_direct_filter_and_name(self):
        from types import SimpleNamespace

        from mainapp.utils.product_public import product_public_dict

        product = SimpleNamespace(
            id=1,
            pk=1,
            category_id=2,
            service_id=None,
            name={"en": "Tank", "ar": "خزان"},
            slug={"en": "tank", "ar": "tank"},
            short_desc={},
            long_desc={},
            meta_title={},
            meta_desc={},
            image="",
            pdf="",
        )
        cat_map = {
            1: SimpleNamespace(id=1, parent_id=None, name={"en": "Boilers"}, slug={"en": "boilers"}),
            2: SimpleNamespace(id=2, parent_id=1, name={"en": "Combi boilers"}, slug={"en": "combi-boilers"}),
        }
        payload = product_public_dict(
            product,
            "en",
            "ar",
            media_url=lambda p: "",
            category_ids=[2],
            categories_map=cat_map,
        )
        self.assertEqual(payload["category_name"], "Combi boilers")
        self.assertEqual(payload["filter_classes"], ["boilers", "combi-boilers"])
        self.assertEqual(payload["filter_class"], "combi-boilers")


class FooterProductCategoriesTests(SimpleTestCase):
    def test_footer_lists_root_categories_only(self):
        from types import SimpleNamespace
        from unittest.mock import MagicMock, patch

        from mainapp.context.home_data import site_footer_product_categories

        root = SimpleNamespace(
            id=1,
            parent_id=None,
            status=True,
            name={"en": "Boilers", "ar": "غلايات"},
            slug={"en": "boilers", "ar": "boilers"},
            short_desc={},
            long_desc={},
            meta_title={},
            meta_desc={},
            image="",
        )
        child = SimpleNamespace(id=2, parent_id=1, status=True)

        fake_qs = MagicMock()
        fake_qs.exclude.return_value.filter.return_value.order_by.return_value = [root]

        with patch("legacydb.model_defs.catalog.categories.Categories.objects", fake_qs):
            with patch(
                "mainapp.utils.category_public.category_public_dict",
                return_value={"name": "Boilers", "url": "/en/products/category/boilers/"},
            ):
                items = site_footer_product_categories("en", "ar")
        self.assertEqual(len(items), 1)
        self.assertEqual(items[0]["name"], "Boilers")
        fake_qs.exclude.return_value.filter.assert_called()


class SiteAddressLocalizationTests(SimpleTestCase):
    def test_site_address_entry_uses_active_language(self):
        from types import SimpleNamespace

        from mainapp.context.contact import site_address_entry

        row = SimpleNamespace(
            id=1,
            title={"en": "Head office", "ar": "المقر الرئيسي"},
            address={
                "en": "Fifth settlement, New Cairo, Egypt",
                "ar": "التجمع الخامس، القاهرة الجديدة، مصر",
            },
            email="info@example.com",
            phone="",
            code="",
            phone2="",
            code2="",
            map_url="",
            map_link="",
            branch_working_hours={},
        )
        ar_entry = site_address_entry(row, "ar", "en")
        en_entry = site_address_entry(row, "en", "ar")
        self.assertEqual(ar_entry["address"], "التجمع الخامس، القاهرة الجديدة، مصر")
        self.assertEqual(en_entry["address"], "Fifth settlement, New Cairo, Egypt")


class DashboardExportEmailTests(SimpleTestCase):
    def test_normalize_monthly_export_day(self):
        from mainapp.utils.dashboard_export_email import normalize_monthly_export_day

        self.assertEqual(normalize_monthly_export_day("1"), 1)
        self.assertEqual(normalize_monthly_export_day("15"), 15)
        self.assertEqual(normalize_monthly_export_day("99"), 28)
        self.assertEqual(normalize_monthly_export_day("bad"), 1)

    def test_should_send_only_on_scheduled_day(self):
        from datetime import date
        from unittest.mock import patch

        from mainapp.utils.dashboard_export_email import should_send_monthly_export_today

        cfg = {"enabled": True, "day": 5, "last_sent": ""}
        with patch("mainapp.utils.dashboard_export_email.monthly_export_settings", return_value=cfg):
            self.assertTrue(should_send_monthly_export_today(today=date(2026, 8, 5)))
            self.assertFalse(should_send_monthly_export_today(today=date(2026, 8, 6)))

    def test_parse_google_drive_folder_id(self):
        from mainapp.utils.google_drive_backup import parse_folder_id, sanitize_service_account_json

        self.assertEqual(
            parse_folder_id("https://drive.google.com/drive/folders/AbC123_-xyz"),
            "AbC123_-xyz",
        )
        self.assertEqual(parse_folder_id("AbC123_-xyz"), "AbC123_-xyz")
        self.assertEqual(parse_folder_id(""), "")
        self.assertEqual(
            sanitize_service_account_json(
                '{"type":"service_account","client_email":"bot@x.iam.gserviceaccount.com","private_key":"x"}'
            ).count("client_email"),
            1,
        )
        self.assertEqual(sanitize_service_account_json("{not json"), "")
        self.assertEqual(sanitize_service_account_json('{"type":"user"}'), "")

    def test_friendly_drive_quota_error_mentions_oauth(self):
        from mainapp.utils.google_drive_backup import friendly_drive_error

        msg = friendly_drive_error(
            Exception(
                "HttpError 403 when requesting ... returned "
                '"Service Accounts do not have storage quota. '
                '<HttpError 403 ... storageQuotaExceeded>"'
            )
        )
        self.assertIn("Connect Google Drive", msg)
        self.assertIn("Shared Drive", msg)

    def test_google_drive_oauth_state_roundtrip(self):
        from mainapp.controllers.dashboard_controller import (
            _sign_google_drive_oauth_state,
            _unsign_google_drive_oauth_state,
        )

        state = _sign_google_drive_oauth_state(7, "https://www.abohagar.co/admin/site-settings/google-drive/callback/")
        payload = _unsign_google_drive_oauth_state(state)
        self.assertEqual(payload["u"], 7)
        self.assertTrue(payload["r"].startswith("https://www.abohagar.co/"))
        self.assertIsNone(_unsign_google_drive_oauth_state("tampered"))

    def test_extract_file_refs_from_path_and_html(self):
        from mainapp.utils.dashboard_media_archive import extract_file_refs

        self.assertEqual(extract_file_refs("products/tank.png"), ["products/tank.png"])
        html = '<p><img src="https://cdn.example.com/a.jpg"></p>'
        refs = extract_file_refs(html)
        self.assertTrue(any(r.endswith("a.jpg") for r in refs))

    def test_build_dashboard_media_archive_zips_uploads(self):
        import tempfile
        from datetime import date
        from pathlib import Path

        from django.test import override_settings

        from mainapp.utils.dashboard_media_archive import build_dashboard_media_archive

        with tempfile.TemporaryDirectory() as tmp:
            media = Path(tmp) / "media"
            backups = Path(tmp) / "backups"
            media.mkdir()
            (media / "products").mkdir()
            (media / "products" / "tank.jpg").write_bytes(b"fake-image")
            with override_settings(MEDIA_ROOT=media, BASE_DIR=Path(tmp)):
                info = build_dashboard_media_archive(on_date=date(2026, 8, 1), include_remote=False)
            self.assertTrue(info["filename"].endswith(".zip"))
            self.assertEqual(info["file_count"], 1)
            self.assertTrue(info["path"].exists())
            self.assertTrue((backups / info["filename"]).exists() or info["path"].parent == backups)

    def test_build_dashboard_export_archive_returns_zip(self):
        from datetime import date

        from mainapp.utils.dashboard_export_archive import build_dashboard_export_archive

        payload, filename, included = build_dashboard_export_archive(on_date=date(2026, 8, 1))
        self.assertTrue(filename.endswith(".zip"))
        self.assertIn("2026-08-01", filename)
        self.assertIn("exports", filename)
        self.assertTrue(payload.startswith(b"PK"))
        self.assertIsInstance(included, list)


class SegmentUrlTests(SimpleTestCase):
    def test_menu_segments_get_trailing_slash(self):
        from mainapp.context_processors import _segment_url

        with override_settings(LANGUAGE_CODE="en"):
            from django.utils import translation

            translation.activate("en")
            self.assertEqual(_segment_url("/about-us"), "/en/about-us/")
            self.assertEqual(_segment_url("/services"), "/en/services/")
            translation.deactivate()


class AlternateLocalePathsTests(SimpleTestCase):
    def test_service_detail_uses_localized_slugs(self):
        from unittest.mock import MagicMock, patch

        service = MagicMock()
        request = MagicMock()
        request.path_info = "/en/services/epc-projects/"

        with patch(
            "mainapp.utils.service_public.get_service_from_path",
            return_value=service,
        ), patch(
            "mainapp.utils.service_public.service_detail_path",
            side_effect=lambda _svc, lang, **_: f"/{lang}/services/slug-{lang}/",
        ), patch("mysite.subpath_utils.resolve_site_subpath", return_value=""):
            localized = alternate_locale_paths(request)

        self.assertEqual(localized["en"], "/en/services/slug-en/")
        self.assertEqual(localized["ar"], "/ar/services/slug-ar/")

    def test_offer_detail_uses_localized_slugs(self):
        from unittest.mock import MagicMock, patch

        offer = MagicMock()
        request = MagicMock()
        request.path_info = "/en/offers/offer-3/"

        with patch(
            "mainapp.utils.offer_public.get_offer_from_path",
            return_value=offer,
        ), patch(
            "mainapp.utils.offer_public.offer_detail_path",
            side_effect=lambda _offer, lang, **_: f"/{lang}/offers/slug-{lang}/",
        ), patch("mysite.subpath_utils.resolve_site_subpath", return_value=""):
            localized = alternate_locale_paths(request)

        self.assertEqual(localized["en"], "/en/offers/slug-en/")
        self.assertEqual(localized["ar"], "/ar/offers/slug-ar/")


class NormalizeScriptNameMiddlewareTests(SimpleTestCase):
    def test_clears_spoofed_script_name_at_domain_root(self):
        def get_response(request):
            self.assertEqual(request.META.get("SCRIPT_NAME"), "")
            return HttpResponse("ok")

        request = RequestFactory().get("/ar/services/test-slug/")
        request.META["SCRIPT_NAME"] = "/ar/services/test-slug"
        response = NormalizeScriptNameMiddleware(get_response)(request)
        self.assertEqual(response.status_code, 200)


class SpiderTrapRedirectMiddlewareTests(SimpleTestCase):
    def test_redirects_recursive_service_path(self):
        with override_settings(ALLOWED_HOSTS=["127.0.0.1"]):
            client = Client(HTTP_HOST="127.0.0.1")
            response = client.get(
                "/ar/services/foo/ar/services/bar/",
                follow=False,
            )
            self.assertEqual(response.status_code, 301)
            self.assertEqual(response["Location"], "/ar/services/bar/")


class DashboardOverviewTests(SimpleTestCase):
    def test_items_skip_zero_and_respect_permission(self):
        from types import SimpleNamespace
        from unittest.mock import MagicMock, patch

        from mainapp.utils.dashboard_overview import build_dashboard_overview_items

        class _FakeQs:
            def __init__(self, count):
                self._count = count

            def count(self):
                return self._count

        nav = {
            "menus": True,
            "entity_services": True,
            "entity_products": True,
        }
        fake_config = {
            "menus": {"title": "Menus", "model": "Menus"},
            "services": {"title": "Services", "model": "Services"},
            "products": {"title": "Products", "model": "Products"},
        }
        legacy = SimpleNamespace(
            Menus=SimpleNamespace(objects=_FakeQs(4)),
            Services=SimpleNamespace(objects=_FakeQs(6)),
            Products=SimpleNamespace(objects=_FakeQs(0)),
        )

        with (
            patch("mainapp.dashboard_acl.user_nav_permissions", return_value=nav),
            patch("mainapp.controllers.dashboard_controller.ENTITY_CONFIG", fake_config),
            patch(
                "mainapp.utils.dashboard_overview._overview_slugs",
                return_value=("menus", "services", "products"),
            ),
            patch("mainapp.utils.dashboard_overview.legacy_models", legacy),
        ):
            items = build_dashboard_overview_items(MagicMock())

        self.assertEqual(len(items), 2)
        self.assertEqual([i["slug"] for i in items], ["menus", "services"])
        for item in items:
            self.assertGreater(item["count"], 0)
            self.assertTrue(item["url"])


class EntityImportMatchTests(SimpleTestCase):
    def _mock_model(self, existing):
        from unittest.mock import MagicMock

        qs = MagicMock()
        qs.order_by.return_value.first.return_value = existing
        model_cls = MagicMock()
        model_cls._meta.fields = [MagicMock(name="slug"), MagicMock(name="name")]
        model_cls._meta.get_field.side_effect = lambda n: MagicMock() if n in {"slug", "name"} else (_ for _ in ()).throw(Exception())
        model_cls.objects.filter.return_value = qs
        return model_cls, qs

    def test_find_by_slug_when_id_missing(self):
        from unittest.mock import patch

        from mainapp.controllers.dashboard_controller import _entity_import_find_instance

        existing = object()
        model_cls, _qs = self._mock_model(existing)
        config = {"json_fields": ["name", "slug"], "sections": [], "form_fields": []}
        row = {"slug_en": "epc-projects"}

        with patch(
            "mainapp.controllers.dashboard_controller._entity_excel_headers",
            return_value=["id", "slug_en", "slug_ar"],
        ):
            found = _entity_import_find_instance(model_cls, config, row)

        self.assertIs(found, existing)
        model_cls.objects.filter.assert_any_call(slug__en="epc-projects")

    def test_find_by_both_names(self):
        from unittest.mock import MagicMock, patch

        from mainapp.controllers.dashboard_controller import _entity_import_find_instance

        existing = object()
        model_cls, qs = self._mock_model(existing)

        def filter_side_effect(**kwargs):
            if kwargs == {"name__en": "Offshore", "name__ar": "بحرية"}:
                return qs
            empty = MagicMock()
            empty.order_by.return_value.first.return_value = None
            return empty

        model_cls.objects.filter.side_effect = filter_side_effect
        config = {"json_fields": ["name", "slug"], "sections": [], "form_fields": []}
        row = {"name_en": "Offshore", "name_ar": "بحرية"}

        with patch(
            "mainapp.controllers.dashboard_controller._entity_excel_headers",
            return_value=["id", "name_en", "name_ar"],
        ):
            found = _entity_import_find_instance(model_cls, config, row)

        self.assertIs(found, existing)


class SitemapLastmodTests(SimpleTestCase):
    def test_format_lastmod_accepts_date_field(self):
        from datetime import date

        from mainapp.utils.sitemap_generator import _format_lastmod

        result = _format_lastmod(date(2026, 5, 26))
        self.assertIsNotNone(result)
        self.assertIn("2026-05-26", result)

    def test_static_routes_include_products_and_gallery(self):
        from mainapp.utils.sitemap_generator import _STATIC_ROUTES

        route_names = {name for name, _, _ in _STATIC_ROUTES}
        self.assertIn("website_products", route_names)
        self.assertIn("website_gallery", route_names)
        self.assertNotIn("website_projects", route_names)


class SiteSettingsExcelTests(SimpleTestCase):
    def test_headers_include_i18n_and_global(self):
        from mainapp.utils.site_settings_excel import site_settings_excel_headers

        headers = site_settings_excel_headers()
        self.assertIn("site_name_en", headers)
        self.assertIn("site_name_ar", headers)
        self.assertIn("google_tag_manager_id", headers)
        self.assertNotIn("smtp_password", headers)


class ContactEmailTemplateTests(SimpleTestCase):
    def test_html_includes_service_field(self):
        from mainapp.utils.contact_email_template import build_contact_notification_bodies

        subject, plain, html = build_contact_notification_bodies(
            site_label="Demo Site",
            record_id=3,
            name="Ahmed",
            email="a@example.com",
            phone="+20100",
            service_label="Teeth whitening",
            message="Hello",
        )
        self.assertIn("Teeth whitening", plain)
        self.assertIn("Teeth whitening", html)
        self.assertIn("Ahmed", subject)

    def test_visitor_reply_includes_admin_text_and_original(self):
        from mainapp.utils.contact_email_template import build_contact_visitor_reply_bodies

        subject, plain, html = build_contact_visitor_reply_bodies(
            site_label="Nile Dental",
            visitor_name="Sara",
            reply_body="We will call you tomorrow.",
            original_message="I need an appointment.",
            record_id=4,
            staff_name="Admin",
        )
        self.assertIn("Nile Dental", subject)
        self.assertIn("We will call you tomorrow.", plain)
        self.assertIn("I need an appointment.", plain)
        self.assertIn("We will call you tomorrow.", html)
        self.assertIn("Admin", plain)


class WhatsappLinkTests(SimpleTestCase):
    def test_egypt_local_mobile(self):
        from mainapp.utils.whatsapp_links import build_wa_me_url, normalize_whatsapp_phone

        self.assertEqual(normalize_whatsapp_phone("01012345678"), "201012345678")
        url = build_wa_me_url("01012345678", "Hello")
        self.assertIn("wa.me/201012345678", url)
        self.assertIn("text=Hello", url)

    def test_international_with_plus(self):
        from mainapp.utils.whatsapp_links import normalize_whatsapp_phone

        self.assertEqual(normalize_whatsapp_phone("+20 10 1234 5678"), "201012345678")


class ServiceImageUrlTests(SimpleTestCase):
    def test_main_image_wins_over_gallery(self):
        from unittest.mock import MagicMock

        from mainapp.utils.service_public import resolve_service_image_url

        service = MagicMock()
        service.id = 1
        service.image = "uploads/main.jpg"

        def media_url(path):
            return f"/media/{path}" if path else ""

        url = resolve_service_image_url(
            service,
            media_url=media_url,
            gallery_image_url="/media/gallery.jpg",
        )
        self.assertEqual(url, "/media/uploads/main.jpg")

    def test_gallery_fallback_when_no_main(self):
        from unittest.mock import MagicMock

        from mainapp.utils.service_public import resolve_service_image_url

        service = MagicMock()
        service.id = 2
        service.image = ""

        url = resolve_service_image_url(
            service,
            media_url=lambda p: f"/m/{p}" if p else "",
            gallery_image_url="/m/gallery.webp",
        )
        self.assertEqual(url, "/m/gallery.webp")


class ContactServiceResolveTests(SimpleTestCase):
    def test_resolve_by_numeric_id(self):
        from unittest.mock import MagicMock, patch

        from mainapp.utils.contact_service import resolve_contact_service

        svc = MagicMock()
        svc.pk = 5
        with patch(
            "mainapp.utils.contact_service.Services.objects.filter",
            return_value=MagicMock(first=MagicMock(return_value=svc)),
        ), patch(
            "mainapp.utils.contact_service.service_display_label",
            return_value="Dental care",
        ):
            row, label = resolve_contact_service("5")
        self.assertIs(row, svc)
        self.assertEqual(label, "Dental care")


class RecaptchaConfigTests(SimpleTestCase):
    def test_disabled_by_default(self):
        from unittest.mock import patch

        from mainapp.utils.recaptcha import recaptcha_public_config

        with patch("mainapp.utils.recaptcha._load_keys", return_value=(False, "", "")):
            cfg = recaptcha_public_config()
        self.assertFalse(cfg["recaptcha_active"])
        self.assertEqual(cfg["recaptcha_site_key"], "")

    def test_widget_with_site_key_only(self):
        from unittest.mock import patch

        from mainapp.utils.recaptcha import recaptcha_public_config

        with patch(
            "mainapp.utils.recaptcha._load_keys",
            return_value=(True, "site-key", ""),
        ):
            cfg = recaptcha_public_config()
        self.assertTrue(cfg["recaptcha_active"])
        self.assertFalse(cfg["recaptcha_verify_enabled"])

    def test_active_when_configured(self):
        from unittest.mock import patch

        from mainapp.utils.recaptcha import recaptcha_public_config

        with patch(
            "mainapp.utils.recaptcha._load_keys",
            return_value=(True, "site-key", "secret"),
        ):
            cfg = recaptcha_public_config()
        self.assertTrue(cfg["recaptcha_active"])
        self.assertTrue(cfg["recaptcha_verify_enabled"])
        self.assertEqual(cfg["recaptcha_site_key"], "site-key")


class GoogleSiteVerificationTests(SimpleTestCase):
    def test_token_only(self):
        from mainapp.utils.analytics_tags import normalize_google_site_verification

        token = "abc123XYZ-_token99"
        self.assertEqual(normalize_google_site_verification(token), token)

    def test_meta_tag_paste(self):
        from mainapp.utils.analytics_tags import normalize_google_site_verification

        meta = '<meta name="google-site-verification" content="my-verification-code" />'
        self.assertEqual(
            normalize_google_site_verification(meta),
            "my-verification-code",
        )

    def test_rejects_invalid(self):
        from mainapp.utils.analytics_tags import normalize_google_site_verification

        self.assertEqual(normalize_google_site_verification("<script>alert(1)</script>"), "")


class EntityImportDateTests(SimpleTestCase):
    def test_parse_datetime_string_from_excel(self):
        from datetime import date

        from mainapp.controllers.dashboard_controller import _parse_import_date

        self.assertEqual(_parse_import_date("2026-05-26 00:00:00"), date(2026, 5, 26))

    def test_parse_iso_date(self):
        from datetime import date

        from mainapp.controllers.dashboard_controller import _parse_import_date

        self.assertEqual(_parse_import_date("2026-05-26"), date(2026, 5, 26))

    def test_parse_datetime_object(self):
        from datetime import date, datetime

        from mainapp.controllers.dashboard_controller import _parse_import_date

        self.assertEqual(
            _parse_import_date(datetime(2026, 5, 26, 0, 0, 0)),
            date(2026, 5, 26),
        )
