"""Tests for audience-lint push gating."""

import io
import json
import os
import pathlib
import shutil
import sys
import tempfile
import unittest
from unittest.mock import patch

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

import backup_manager
import mockup_manager
import push_to_canvas
import staging_manager

TEST_COURSE_ID = "99001"
PLUGIN_ROOT = pathlib.Path(__file__).parent.parent


def _mock_config():
    return {
        "token": "fake",
        "domain": "canvas.test.edu",
        "course_id": TEST_COURSE_ID,
        "headers": {"Authorization": "Bearer fake"},
    }


class TestPushAudienceGate(unittest.TestCase):
    def setUp(self):
        self.tmp = tempfile.TemporaryDirectory()
        self.tmp_root = pathlib.Path(self.tmp.name)
        self._orig_sm_root = staging_manager.get_plugin_root
        self._orig_bm_root = backup_manager.get_plugin_root
        staging_manager.get_plugin_root = lambda: self.tmp_root
        backup_manager.get_plugin_root = lambda: self.tmp_root
        self._timeline_patcher = patch.object(
            push_to_canvas, "_emit_timeline_event")
        self._progress_patcher = patch.object(
            push_to_canvas, "_track_progress")
        self._timeline_patcher.start()
        self._progress_patcher.start()

    def tearDown(self):
        self._progress_patcher.stop()
        self._timeline_patcher.stop()
        staging_manager.get_plugin_root = self._orig_sm_root
        backup_manager.get_plugin_root = self._orig_bm_root
        shutil.rmtree(PLUGIN_ROOT / "courses" / TEST_COURSE_ID,
                      ignore_errors=True)
        self.tmp.cleanup()

    def _stage_page(self, slug, html, audience=None):
        staging_manager.stage(
            TEST_COURSE_ID, "pages", slug, html, validate=False)
        if audience is not None:
            path = staging_manager._staged_path(TEST_COURSE_ID, "pages", slug)
            with open(path + ".meta.json", "w", encoding="utf-8") as f:
                json.dump({"audience": audience}, f)

    def test_student_page_with_v3_blocks_before_canvas_calls(self):
        self._stage_page("blocked-page", "<p>Draft v3</p>", audience="student")
        with patch("push_to_canvas.canvas_api.get_page") as mock_get, \
             patch("push_to_canvas.canvas_api.update_page") as mock_update:
            result = push_to_canvas.push_page(
                _mock_config(), "blocked-page", TEST_COURSE_ID)

        self.assertFalse(result["ok"])
        self.assertIn("audience_violations", result)
        self.assertEqual(result["audience_violations"][0]["token"], "v3")
        mock_get.assert_not_called()
        mock_update.assert_not_called()

    def test_faculty_page_with_hpi_blocks(self):
        self._stage_page(
            "faculty-page", "<p>HPI/ID rationale</p>", audience="faculty")
        result = push_to_canvas.push_page(
            _mock_config(), "faculty-page", TEST_COURSE_ID)

        self.assertFalse(result["ok"])
        tokens = [v["token"] for v in result["audience_violations"]]
        self.assertIn("HPI/ID", tokens)

    def test_clean_page_pushes(self):
        self._stage_page(
            "clean-page", "<h2>Welcome</h2><p>Course overview.</p>",
            audience="student")
        verify_resp = {
            "ok": True,
            "canvas_url": "https://canvas.test.edu/courses/99001/pages/clean-page",
            "content_length": 40,
            "warnings": [],
        }
        with patch("push_to_canvas.canvas_api.get_page",
                   return_value={"body": "<p>old</p>"}), \
             patch("push_to_canvas.canvas_api.update_page",
                   return_value=True) as mock_update, \
             patch("push_to_canvas.post_write_verify.verify_page",
                   return_value=verify_resp), \
             patch("push_to_canvas.backup_manager.save_backup"):
            result = push_to_canvas.push_page(
                _mock_config(), "clean-page", TEST_COURSE_ID)

        self.assertTrue(result["ok"])
        mock_update.assert_called_once()

    def test_allow_audience_violations_warns_and_pushes(self):
        self._stage_page("warn-page", "<p>Draft v3</p>", audience="student")
        verify_resp = {
            "ok": True,
            "canvas_url": "https://canvas.test.edu/courses/99001/pages/warn-page",
            "content_length": 20,
            "warnings": [],
        }
        stderr = io.StringIO()
        with patch("sys.stderr", stderr), \
             patch("push_to_canvas.canvas_api.get_page",
                   return_value={"body": "<p>old</p>"}), \
             patch("push_to_canvas.canvas_api.update_page",
                   return_value=True) as mock_update, \
             patch("push_to_canvas.post_write_verify.verify_page",
                   return_value=verify_resp), \
             patch("push_to_canvas.backup_manager.save_backup"):
            result = push_to_canvas.push_page(
                _mock_config(), "warn-page", TEST_COURSE_ID,
                allow_audience_violations=True)

        self.assertTrue(result["ok"])
        self.assertIn("audience_violations", result)
        self.assertIn("WARNING", stderr.getvalue())
        mock_update.assert_called_once()

    def test_id_audience_never_blocks(self):
        self._stage_page("id-page", "<p>Draft v3 HPI/ID</p>", audience="id")
        verify_resp = {
            "ok": True,
            "canvas_url": "https://canvas.test.edu/courses/99001/pages/id-page",
            "content_length": 20,
            "warnings": [],
        }
        with patch("push_to_canvas.canvas_api.get_page",
                   return_value={"body": "<p>old</p>"}), \
             patch("push_to_canvas.canvas_api.update_page",
                   return_value=True), \
             patch("push_to_canvas.post_write_verify.verify_page",
                   return_value=verify_resp), \
             patch("push_to_canvas.backup_manager.save_backup"):
            result = push_to_canvas.push_page(
                _mock_config(), "id-page", TEST_COURSE_ID)

        self.assertTrue(result["ok"])
        self.assertNotIn("audience_violations", result)

    def test_missing_meta_defaults_student_and_blocks(self):
        self._stage_page("missing-meta", "<p>Draft HPI/ID</p>")
        with patch("push_to_canvas.canvas_api.get_page") as mock_get:
            result = push_to_canvas.push_page(
                _mock_config(), "missing-meta", TEST_COURSE_ID)

        self.assertFalse(result["ok"])
        self.assertEqual(result["audience"], "student")
        mock_get.assert_not_called()

    def test_quiz_question_stem_violation_blocks(self):
        staging_manager.stage(
            TEST_COURSE_ID,
            "quizzes",
            "quiz-1",
            {
                "quiz": {"title": "Quiz", "description": "Clean intro"},
                "questions": [
                    {
                        "question_name": "Q1",
                        "question_text": "<p>Which option fits draft v3?</p>",
                        "question_type": "multiple_choice_question",
                        "points_possible": 1,
                        "answers": [{"answer_text": "A", "answer_weight": 100}],
                    }
                ],
            },
            validate=False,
        )
        with patch("push_to_canvas.canvas_api.get_quiz") as mock_get_quiz:
            result = push_to_canvas.push_quiz(
                _mock_config(), "quiz-1", TEST_COURSE_ID)

        self.assertFalse(result["ok"])
        self.assertEqual(result["audience_violations"][0]["token"], "v3")
        mock_get_quiz.assert_not_called()


class TestLintContentUnit(unittest.TestCase):
    def test_empty_html_clean(self):
        self.assertEqual(mockup_manager.lint_content("", "student"), [])

    def test_multi_token_finds_all(self):
        result = mockup_manager.lint_content(
            "<p>Proposal v3 uses SLA and HPI/ID framing.</p>", "student")
        tokens = {v["token"] for v in result}
        self.assertIn("v3", tokens)
        self.assertIn("SLA", tokens)
        self.assertIn("HPI/ID", tokens)

    def test_bct_taxonomy_v1_not_flagged(self):
        result = mockup_manager.lint_content(
            "Michie BCT Taxonomy v1 contains 93 BCTs.", "faculty")
        tokens = {v["token"] for v in result}
        self.assertNotIn("v1", tokens)


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