from importlib import import_module
from pathlib import Path

from django.forms import modelform_factory
from django.template import Context, Template
from django.template.loader import get_template
from django.test import SimpleTestCase

from ocyan.plugin.coyote.blocks.content import CoyoteTabsBlock
from ocyan.plugin.coyote.forms import StyleSettingsForm
from ocyan.plugin.coyote.models import (
    CoyoteContactPage,
    CoyoteHomePage,
    StyleSettings,
)


class CoyoteSmokeTests(SimpleTestCase):
    LEGACY_THEME_IMPORT_MIGRATIONS = {
        "0010_auto_20231127_1018.py",
        "0014_remove_stylesettings_site_and_more.py",
        "0017_stylesettings_body_color_stylesettings_border_radius.py",
        "0018_alter_stylesettings_border_radius.py",
        "0019_auto_20250512_1642.py",
        "0020_stylesettings_header.py",
        "0021_fix_incorrect_header_default_setting.py",
    }

    def test_app_import(self):
        module = import_module("ocyan.plugin.coyote")
        self.assertTrue(hasattr(module, "CoyoteConfig"))

    def test_app_config_import(self):
        module = import_module("ocyan.plugin.coyote.apps")
        self.assertEqual(
            module.OcyanPluginCoyoteAppConfig.name,
            "ocyan.plugin.coyote",
        )

    def test_models_import(self):
        self.assertEqual(CoyoteHomePage.__name__, "CoyoteHomePage")
        self.assertEqual(CoyoteContactPage.__name__, "CoyoteContactPage")
        self.assertEqual(StyleSettings.__name__, "StyleSettings")

    def test_block_import(self):
        self.assertEqual(CoyoteTabsBlock.__name__, "CoyoteTabsBlock")

    def test_settings_form_import(self):
        self.assertEqual(StyleSettingsForm.__name__, "StyleSettingsForm")

    def test_style_settings_form_labels_are_client_neutral(self):
        form_class = modelform_factory(
            StyleSettings, form=StyleSettingsForm, fields="__all__"
        )
        form = form_class()

        self.assertEqual(str(form.fields["primary"].label), "Primary color")
        self.assertEqual(str(form.fields["secondary"].label), "Secondary color")
        self.assertEqual(str(form.fields["gray_900"].label), "Dark accent color")
        self.assertEqual(str(form.fields["gray_200"].label), "Light accent color")
        self.assertEqual(str(form.fields["body_color"].label), "Body text color")
        self.assertEqual(
            str(form.fields["body_background"].label), "Body background color"
        )
        self.assertEqual(
            str(form.fields["footer_background"].label), "Footer background color"
        )
        self.assertEqual(
            str(form.fields["header_background"].label), "Header background color"
        )
        self.assertEqual(
            str(form.fields["header_font_family"].help_text),
            "Used for headings across the Coyote theme.",
        )
        self.assertEqual(
            str(form.fields["paragraph_font_family"].help_text),
            "Used for paragraph text across the Coyote theme.",
        )
        self.assertEqual(str(form.fields["border_radius"].label), "Border radius")

    def test_style_settings_source_uses_translatable_english_panel_headings(self):
        models_source = (
            Path(__file__).resolve().parent.parent / "models.py"
        ).read_text(encoding="utf-8")
        self.assertIn('heading=_("Colors")', models_source)
        self.assertIn('heading=_("Typography")', models_source)
        self.assertIn('heading=_("General")', models_source)
        self.assertNotIn('heading="Kleuren"', models_source)
        self.assertNotIn('heading="Typografie"', models_source)
        self.assertNotIn('heading="Overig"', models_source)

    def test_live_editor_labels_use_normalized_english_copy(self):
        models_source = (
            Path(__file__).resolve().parent.parent / "models.py"
        ).read_text(encoding="utf-8")
        stream_blocks_source = (
            Path(__file__).resolve().parent.parent / "stream_blocks.py"
        ).read_text(encoding="utf-8")
        content_blocks_source = (
            Path(__file__).resolve().parent.parent / "blocks/content.py"
        ).read_text(encoding="utf-8")
        partial_blocks_source = (
            Path(__file__).resolve().parent.parent / "blocks/partials.py"
        ).read_text(encoding="utf-8")

        self.assertIn('verbose_name=_("Content")', models_source)
        self.assertNotIn('verbose_name="Inhoud"', models_source)
        self.assertIn('_("Paragraph columns")', stream_blocks_source)
        self.assertNotIn('_("Paragraph colums")', stream_blocks_source)
        self.assertIn('_("Logos")', stream_blocks_source)
        self.assertIn('_("Logos")', content_blocks_source)
        self.assertNotIn('_("Logo\\\'s")', stream_blocks_source)
        self.assertNotIn('_("Logo\\\'s")', content_blocks_source)
        self.assertIn('_("LinkedIn")', content_blocks_source)
        self.assertNotIn('_("Linkedin")', content_blocks_source)
        self.assertIn('_("Page URL")', partial_blocks_source)
        self.assertNotIn('_("Page url")', partial_blocks_source)

    def test_public_template_fallback_copy_is_translatable(self):
        category_template = (
            Path(__file__).resolve().parent.parent
            / "templates/coyote/blocks/content/categoryblock.html"
        ).read_text(encoding="utf-8")
        tabs_template = (
            Path(__file__).resolve().parent.parent
            / "templates/coyote/blocks/content/tabsblock.html"
        ).read_text(encoding="utf-8")

        self.assertIn('{% trans "Products" %}', category_template)
        self.assertNotIn("Producten", category_template)
        self.assertIn("{% load wagtailcore_tags ocyan_thumbnail i18n %}", tabs_template)
        self.assertIn("default:_('Tabbed content')", tabs_template)
        self.assertIn("default:_('Tabbed content image')", tabs_template)
        self.assertNotIn("default:'Tabbed content'", tabs_template)
        self.assertNotIn("default:'Tabbed content image'", tabs_template)

    def test_theme_extension_imports(self):
        module = import_module("ocyan.plugin.coyote.theme_extension")
        self.assertIsNotNone(module)

    def test_template_pages_load(self):
        self.assertIsNotNone(get_template("coyote/pages/home.html"))
        self.assertIsNotNone(get_template("coyote/pages/contact.html"))
        self.assertIsNotNone(get_template("coyote/coyote_page.html"))

    def test_theme_extension_template_loads(self):
        self.assertIsNotNone(get_template("base.html"))

    def test_compiled_public_css_assets_exist(self):
        static_dir = Path(__file__).resolve().parent.parent / "static/coyote/css"
        self.assertTrue((static_dir / "base.css").exists())
        self.assertTrue((static_dir / "homepage/base.css").exists())
        self.assertTrue((static_dir / "contact/base.css").exists())

    def test_public_style_partials_reference_compiled_css_by_default(self):
        template_root = (
            Path(__file__).resolve().parent.parent / "templates/coyote/partials/styles"
        )
        base_template = (template_root / "base_styles.html").read_text(encoding="utf-8")
        home_template = (template_root / "home_styles.html").read_text(encoding="utf-8")
        contact_template = (template_root / "contact_styles.html").read_text(
            encoding="utf-8"
        )

        self.assertIn("coyote/css/base.css", base_template)
        self.assertIn("coyote/css/homepage/base.css", home_template)
        self.assertIn("coyote/css/contact/base.css", contact_template)
        self.assertNotIn('type="text/x-scss"', base_template)
        self.assertNotIn('type="text/x-scss"', home_template)
        self.assertNotIn('type="text/x-scss"', contact_template)

    def test_readme_documents_scss_source_and_host_override_contract(self):
        readme = (Path(__file__).resolve().parents[4] / "README.md").read_text(
            encoding="utf-8"
        )
        self.assertIn("SCSS remains the source of truth", readme)
        self.assertIn("static/coyote/base.scss", readme)
        self.assertIn("static/coyote/homepage/base.scss", readme)
        self.assertIn("static/coyote/contact/base.scss", readme)
        self.assertIn("Host override / rebuild contract", readme)
        self.assertIn("django-libsass", readme)
        self.assertIn("static/coyote/css/base.css", readme)

    def test_admin_widget_modules_import(self):
        self.assertIsNotNone(import_module("ocyan.plugin.coyote.custom_blocks.widgets"))
        self.assertIsNotNone(
            import_module("ocyan.plugin.coyote.custom_blocks.adapters")
        )
        self.assertIsNotNone(import_module("ocyan.plugin.coyote.custom_blocks"))

    def test_font_select_widget_template_uses_translatable_copy(self):
        template_path = (
            Path(__file__).resolve().parent.parent
            / "templates/coyote/forms/widgets/fontselect.html"
        )
        content = template_path.read_text(encoding="utf-8")
        self.assertIn('{% trans "Choose a font..." %}', content)
        self.assertNotIn("Kies een font", content)

    def test_coyote_uses_modern_wagtail_admin_telepath_imports(self):
        adapters_source = (
            Path(__file__).resolve().parent.parent / "custom_blocks/adapters.py"
        ).read_text(encoding="utf-8")
        blocks_source = (
            Path(__file__).resolve().parent.parent / "custom_blocks/__init__.py"
        ).read_text(encoding="utf-8")

        self.assertIn(
            "from wagtail.admin.telepath.widgets import WidgetAdapter",
            adapters_source,
        )
        self.assertNotIn(
            "from wagtail.widget_adapters import WidgetAdapter", adapters_source
        )
        self.assertIn("from wagtail.admin.telepath import register", blocks_source)
        self.assertNotIn("from wagtail.telepath import register", blocks_source)

    def test_readme_documents_migration_caveats_and_legacy_theme_default_risk(self):
        readme = (Path(__file__).resolve().parents[4] / "README.md").read_text(
            encoding="utf-8"
        )
        self.assertIn("0024_remove_coyotecontactpage_page_ptr_and_more", readme)
        self.assertIn("0025_remove_coyotecontactpage_page_ptr_and_more", readme)
        self.assertIn("must not be copied into any future", readme)
        self.assertIn("migration pattern", readme)
        self.assertIn("Migration policy", readme)
        self.assertIn("freeze the future without rewriting", readme)

    def test_only_known_historical_migrations_import_live_theme_defaults(self):
        migration_dir = Path(__file__).resolve().parent.parent / "migrations"
        files_with_live_theme_imports = sorted(
            path.name
            for path in migration_dir.glob("*.py")
            if "from oxyan.themes.definitions import theme"
            in path.read_text(encoding="utf-8")
        )

        self.assertEqual(
            files_with_live_theme_imports,
            sorted(self.LEGACY_THEME_IMPORT_MIGRATIONS),
        )

    def test_readme_documents_public_asset_ownership_boundary(self):
        readme = (Path(__file__).resolve().parents[4] / "README.md").read_text(
            encoding="utf-8"
        )
        self.assertIn("Public asset ownership", readme)
        self.assertIn("ships verified compiled fallback/default assets", readme)
        self.assertIn("webpack.config.js", readme)
        self.assertIn("templates/coyote/partials/styles/base_styles.html", readme)

    def test_tabs_template_uses_unique_aria_linkage(self):
        template_path = (
            Path(__file__).resolve().parent.parent
            / "templates/coyote/blocks/content/tabsblock.html"
        )
        content = template_path.read_text(encoding="utf-8")
        self.assertIn('id="{{ tab_id }}"', content)
        self.assertIn('aria-controls="{{ panel_id }}"', content)
        self.assertIn('aria-labelledby="{{ tab_id }}"', content)
        self.assertNotIn('id="pills-home-tab"', content)

    def test_accordion_template_uses_button_control(self):
        template_path = (
            Path(__file__).resolve().parent.parent
            / "templates/coyote/blocks/content/accordion.html"
        )
        content = template_path.read_text(encoding="utf-8")
        self.assertIn("<button", content)
        self.assertIn('class="accordion-button collapsed p-3"', content)
        self.assertIn('aria-controls="{{ collapse_id }}"', content)
        self.assertNotIn('class="accordion-header d-flex collapsed p-3"', content)

    def test_image_alt_and_link_fallback_fixes_present(self):
        category_template = (
            Path(__file__).resolve().parent.parent
            / "templates/coyote/blocks/content/categoryblock.html"
        ).read_text(encoding="utf-8")
        product_template = (
            Path(__file__).resolve().parent.parent
            / "templates/coyote/partials/product_slider_item.html"
        ).read_text(encoding="utf-8")
        info_template = (
            Path(__file__).resolve().parent.parent
            / "templates/coyote/blocks/content/info_block.html"
        ).read_text(encoding="utf-8")
        self.assertIn("alt=cat.category.name", category_template)
        self.assertIn(
            'aria-label="{{ product.title|default:product.get_title }}"',
            product_template,
        )
        self.assertNotIn('href=""', info_template)

    def test_public_page_assets_use_static_files_without_inline_slider_script(self):
        coyote_page = (
            Path(__file__).resolve().parent.parent / "templates/coyote/coyote_page.html"
        ).read_text(encoding="utf-8")
        home_page = (
            Path(__file__).resolve().parent.parent / "templates/coyote/pages/home.html"
        ).read_text(encoding="utf-8")
        contact_page = (
            Path(__file__).resolve().parent.parent
            / "templates/coyote/pages/contact.html"
        ).read_text(encoding="utf-8")
        base_styles_partial = (
            Path(__file__).resolve().parent.parent
            / "templates/coyote/partials/styles/base_styles.html"
        ).read_text(encoding="utf-8")
        home_styles_partial = (
            Path(__file__).resolve().parent.parent
            / "templates/coyote/partials/styles/home_styles.html"
        ).read_text(encoding="utf-8")
        contact_styles_partial = (
            Path(__file__).resolve().parent.parent
            / "templates/coyote/partials/styles/contact_styles.html"
        ).read_text(encoding="utf-8")

        self.assertIn("coyote/js/page_sliders.js", coyote_page)
        self.assertNotIn("oxyan.initSliders", coyote_page)
        self.assertIn(
            '{% include "coyote/partials/styles/base_styles.html" %}', coyote_page
        )
        self.assertIn(
            '{% include "coyote/partials/styles/home_styles.html" %}', home_page
        )
        self.assertIn(
            '{% include "coyote/partials/styles/contact_styles.html" %}',
            contact_page,
        )
        self.assertIn("coyote/css/base.css", base_styles_partial)
        self.assertIn("coyote/css/homepage/base.css", home_styles_partial)
        self.assertIn("coyote/css/contact/base.css", contact_styles_partial)
        self.assertIn(
            "Source SCSS entrypoint: static/coyote/base.scss", base_styles_partial
        )
        self.assertIn(
            "Source SCSS entrypoint: static/coyote/homepage/base.scss",
            home_styles_partial,
        )
        self.assertIn(
            "Source SCSS entrypoint: static/coyote/contact/base.scss",
            contact_styles_partial,
        )

    def test_public_slider_asset_exists(self):
        slider_asset = (
            Path(__file__).resolve().parent.parent / "static/coyote/js/page_sliders.js"
        )
        self.assertTrue(slider_asset.exists())

    def test_public_style_partials_load(self):
        self.assertIsNotNone(get_template("coyote/partials/styles/base_styles.html"))
        self.assertIsNotNone(get_template("coyote/partials/styles/home_styles.html"))
        self.assertIsNotNone(get_template("coyote/partials/styles/contact_styles.html"))

    def test_optional_url_tag_returns_empty_when_missing(self):
        from ocyan.plugin.coyote.templatetags.coyote_tags import optional_url

        self.assertEqual(optional_url("missing:view-name"), "")

    def test_render_optional_template_returns_empty_when_missing(self):
        from ocyan.plugin.coyote.templatetags.coyote_tags import (
            render_optional_template,
        )

        template_context = {}
        self.assertEqual(
            render_optional_template(template_context, "missing/template.html"), ""
        )

    def test_render_optional_template_renders_existing_template(self):
        template = Template(
            "{% load coyote_tags %}"
            "{% render_optional_template 'coyote/partials/fonts.html' font_settings=font_settings %}"
        )
        font_settings = type(
            "FontSettings",
            (),
            {
                "header_font_family": "",
                "paragraph_font_family": "",
                "header_font": "",
                "header_font_variant": "",
                "paragraph_font": "",
                "paragraph_font_variant": "",
            },
        )()
        rendered = template.render(Context({"font_settings": font_settings}))
        self.assertEqual(rendered.strip(), "")

    def test_dependency_boundaries_are_template_guarded(self):
        accordion_template = (
            Path(__file__).resolve().parent.parent
            / "templates/coyote/blocks/content/accordion.html"
        ).read_text(encoding="utf-8")
        contact_template = (
            Path(__file__).resolve().parent.parent
            / "templates/coyote/blocks/content/contact.html"
        ).read_text(encoding="utf-8")
        category_template = (
            Path(__file__).resolve().parent.parent
            / "templates/coyote/blocks/content/categoryblock.html"
        ).read_text(encoding="utf-8")
        product_template = (
            Path(__file__).resolve().parent.parent
            / "templates/coyote/partials/product_slider_item.html"
        ).read_text(encoding="utf-8")

        self.assertIn(
            "load ocyan_thumbnail wagtailcore_tags styling_tags coyote_tags",
            accordion_template,
        )
        self.assertNotIn("roadrunner_tags", accordion_template)
        self.assertIn(
            "optional_url 'contact_form:contact-form-handler'", contact_template
        )
        self.assertIn(
            "render_optional_template 'oscar/partials/form_field.html'",
            contact_template,
        )
        self.assertIn(
            'render_optional_template "oscar/catalogue/partials/stock_record.html"',
            category_template,
        )
        self.assertNotIn(
            '{% extends "oscar/catalogue/partials/product.html" %}',
            product_template,
        )

    def test_contact_template_fallback_message_is_present(self):
        contact_template = (
            Path(__file__).resolve().parent.parent
            / "templates/coyote/blocks/content/contact.html"
        ).read_text(encoding="utf-8")
        self.assertIn("The contact form is currently unavailable.", contact_template)
