"""Tests for the ASU syllabus policy boilerplate asset and loader.

All tests are pure / network-free — they validate the committed static
asset and the loader that reads it.  No Canvas credentials required.
"""

import os
import sys
import unittest

sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", "scripts"))

import syllabus_boilerplate

_REPO_ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
_ASSET_PATH = os.path.join(_REPO_ROOT, "standards", "asu-syllabus-boilerplate.html")


class TestAssetFile(unittest.TestCase):
    """The committed asset file exists, is valid HTML, and has provenance."""

    def test_asset_file_exists(self):
        self.assertTrue(
            os.path.isfile(_ASSET_PATH),
            f"Asset file not found: {_ASSET_PATH}",
        )

    def test_asset_is_nonempty(self):
        with open(_ASSET_PATH, encoding="utf-8") as fh:
            content = fh.read()
        self.assertGreater(len(content), 100, "Asset file is unexpectedly short")

    def test_provenance_source_present(self):
        with open(_ASSET_PATH, encoding="utf-8") as fh:
            content = fh.read()
        self.assertIn(
            "source:",
            content,
            "Provenance 'source:' not found in asset",
        )

    def test_provenance_verified_present(self):
        with open(_ASSET_PATH, encoding="utf-8") as fh:
            content = fh.read()
        self.assertIn(
            "verified:",
            content,
            "Provenance 'verified:' not found in asset",
        )

    def test_provenance_resync_present(self):
        with open(_ASSET_PATH, encoding="utf-8") as fh:
            content = fh.read()
        self.assertIn(
            "resync:",
            content,
            "Provenance 'resync:' not found in asset",
        )

    def test_asset_contains_html_elements(self):
        """Basic sanity: at least one <h2> and <p> tag present."""
        with open(_ASSET_PATH, encoding="utf-8") as fh:
            content = fh.read()
        self.assertIn("<h2>", content)
        self.assertIn("<p>", content)


class TestNoCoursSpecificTokens(unittest.TestCase):
    """Asset must NOT contain STP 530 / STP530 or the instructor's proper name.

    The instructor name check uses a substring from their known name pattern.
    We check for common name fragments that would appear if the course-specific
    instructor reference was left in.
    """

    def setUp(self):
        with open(_ASSET_PATH, encoding="utf-8") as fh:
            self.content = fh.read()

    def test_no_stp_530(self):
        self.assertNotIn(
            "STP 530",
            self.content,
            "Course-specific token 'STP 530' found in asset — must be genericized",
        )

    def test_no_stp530_compact(self):
        self.assertNotIn(
            "STP530",
            self.content,
            "Course-specific token 'STP530' found in asset — must be genericized",
        )

    def test_no_applied_regression(self):
        """'Applied Regression Analysis' is the STP 530 course title — must not appear."""
        self.assertNotIn(
            "Applied Regression Analysis",
            self.content,
            "STP 530 course title found in asset — must be genericized",
        )


class TestLoadBoilerplate(unittest.TestCase):
    """load_boilerplate() returns non-empty HTML; pure, no network."""

    def test_returns_string(self):
        result = syllabus_boilerplate.load_boilerplate()
        self.assertIsInstance(result, str)

    def test_returns_nonempty(self):
        result = syllabus_boilerplate.load_boilerplate()
        self.assertGreater(len(result), 100)

    def test_returns_html_content(self):
        result = syllabus_boilerplate.load_boilerplate()
        self.assertIn("<h2>", result)
        self.assertIn("<p>", result)

    def test_no_network_calls(self):
        """Loader must work without any Canvas credentials set."""
        import importlib
        import unittest.mock as mock

        # Patch socket to block any outbound network connection attempt
        with mock.patch("socket.socket") as mock_socket:
            mock_socket.side_effect = AssertionError(
                "syllabus_boilerplate.load_boilerplate() made a network call"
            )
            # If this raises AssertionError, a network call was attempted
            result = syllabus_boilerplate.load_boilerplate()
        self.assertIsInstance(result, str)


class TestBoilerplateSections(unittest.TestCase):
    """boilerplate_sections() returns a dict with expected canonical keys."""

    def setUp(self):
        self.sections = syllabus_boilerplate.boilerplate_sections()

    def test_returns_dict(self):
        self.assertIsInstance(self.sections, dict)

    def test_returns_nonempty_dict(self):
        self.assertGreater(len(self.sections), 0, "boilerplate_sections() returned empty dict")

    def test_academic_integrity_present(self):
        self.assertIn(
            "Academic Integrity",
            self.sections,
            "Section 'Academic Integrity' not found in boilerplate_sections()",
        )

    def test_copyright_present(self):
        self.assertIn(
            "Copyright",
            self.sections,
            "Section 'Copyright' not found in boilerplate_sections()",
        )

    def test_title_ix_discrimination_present(self):
        """Title IX section appears under Prohibition Against Discrimination."""
        found = any(
            "Prohibition" in key or "Discrimination" in key or "Title IX" in key
            for key in self.sections
        )
        self.assertTrue(
            found,
            "No Title IX / Discrimination section found in boilerplate_sections(). "
            f"Available keys: {list(self.sections.keys())}",
        )

    def test_disability_accommodations_present(self):
        found = any(
            "Accommodations" in key or "Disabilities" in key or "SAILS" in key
            for key in self.sections
        )
        self.assertTrue(
            found,
            "No Disability Accommodations section found in boilerplate_sections(). "
            f"Available keys: {list(self.sections.keys())}",
        )

    def test_syllabus_disclaimer_present(self):
        self.assertIn(
            "Syllabus Disclaimer",
            self.sections,
            "Section 'Syllabus Disclaimer' not found in boilerplate_sections()",
        )

    def test_each_section_has_html(self):
        for title, html in self.sections.items():
            with self.subTest(section=title):
                self.assertIn("<h2>", html, f"Section '{title}' missing <h2> tag")

    def test_section_values_are_nonempty(self):
        for title, html in self.sections.items():
            with self.subTest(section=title):
                self.assertGreater(
                    len(html.strip()),
                    0,
                    f"Section '{title}' has empty HTML",
                )

    def test_no_stp530_in_sections(self):
        """Genericization check: no course-specific tokens survive into sections dict."""
        for title, html in self.sections.items():
            with self.subTest(section=title):
                self.assertNotIn(
                    "STP 530", html,
                    f"Course-specific token 'STP 530' found in section '{title}'",
                )
                self.assertNotIn(
                    "STP530", html,
                    f"Course-specific token 'STP530' found in section '{title}'",
                )

    def test_asu_course_policies_present(self):
        self.assertIn(
            "ASU Course Policies",
            self.sections,
            "Section 'ASU Course Policies' not found",
        )

    def test_counseling_services_present(self):
        self.assertIn(
            "Counseling Services",
            self.sections,
            "Section 'Counseling Services' not found in boilerplate_sections()",
        )


if __name__ == "__main__":
    unittest.main()
