"""Regression tests for canvas_api.py request and discussion defaults."""

import os
import sys
import unittest
from unittest.mock import MagicMock, patch

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

import canvas_api


def _mock_config():
    return {
        "token": "fake",
        "domain": "canvas.test.edu",
        "course_id": "99999",
        "base_url": "https://canvas.test.edu/api/v1",
        "course_url": "https://canvas.test.edu/api/v1/courses/99999",
        "headers": {"Authorization": "Bearer fake"},
    }


class TestCreateDiscussionDefaults(unittest.TestCase):
    @patch("canvas_api._request_with_retry")
    def test_create_discussion_defaults_to_unpublished(self, mock_request):
        response = MagicMock()
        response.status_code = 201
        response.json.return_value = {"id": 42, "title": "Test"}
        mock_request.return_value = response

        canvas_api.create_discussion(_mock_config(), "Test Discussion")

        payload = mock_request.call_args.kwargs["json"]
        self.assertIs(payload["published"], False)


class TestRequestWithRetry(unittest.TestCase):
    @patch("canvas_api.time.sleep", return_value=None)
    def test_request_with_retry_does_not_retry_on_403(self, mock_sleep):
        response = MagicMock()
        response.status_code = 403
        response.headers = {}

        method = MagicMock(return_value=response)

        result = canvas_api._request_with_retry(
            method, "https://canvas.test.edu/api/v1/courses/99999", {}
        )

        self.assertIs(result, response)
        self.assertEqual(method.call_count, 1)
        _, kwargs = method.call_args
        self.assertEqual(kwargs["timeout"], 30)
        mock_sleep.assert_not_called()


class TestGetSyllabusContract(unittest.TestCase):
    """Verify get_syllabus passes the correct include param and returns the 'html' key.

    Regression guard for the assurance_audit false-negative bug (course 256162):
    _fetch_syllabus_text was reading .get("syllabus_body") but get_syllabus always
    returns {"html": ..., "page_url": ..., "updated_at": ...}.
    """

    @patch("canvas_api._request_with_retry")
    def test_get_syllabus_sends_include_param(self, mock_request):
        """get_syllabus must request include[]=syllabus_body."""
        response = MagicMock()
        response.status_code = 200
        response.json.return_value = {
            "syllabus_body": "<p>Use of generative AI tools is permitted.</p>",
            "html_url": "https://canvas.test.edu/courses/99999",
            "updated_at": "2026-01-01T00:00:00Z",
        }
        mock_request.return_value = response

        canvas_api.get_syllabus(_mock_config())

        _, call_kwargs = mock_request.call_args
        self.assertEqual(call_kwargs["params"], {"include[]": "syllabus_body"})

    @patch("canvas_api._request_with_retry")
    def test_get_syllabus_returns_html_key_not_syllabus_body(self, mock_request):
        """get_syllabus must return dict with 'html' key containing the syllabus body."""
        response = MagicMock()
        response.status_code = 200
        response.json.return_value = {
            "syllabus_body": "<p>Use of generative AI tools is permitted.</p>",
            "html_url": "https://canvas.test.edu/courses/99999",
            "updated_at": "2026-01-01T00:00:00Z",
        }
        mock_request.return_value = response

        result = canvas_api.get_syllabus(_mock_config())

        # Must expose 'html', never 'syllabus_body' (the raw Canvas field name)
        self.assertIn("html", result)
        self.assertNotIn("syllabus_body", result)
        self.assertIn("generative AI", result["html"])


class TestCreateLockedModule(unittest.TestCase):
    """Tests for create_locked_module helper."""

    def _items(self):
        return [
            {"title": "Overview", "type": "Page", "ref": "overview-slug",
             "completion": "must_view"},
            {"title": "Lecture", "type": "Page", "ref": "lecture-slug",
             "completion": "must_view"},
            {"title": "Quiz 1", "type": "Assignment", "ref": 42,
             "completion": "must_submit"},
        ]

    @patch("canvas_api.publish_module")
    @patch("canvas_api.add_module_item")
    @patch("canvas_api.create_module")
    @patch("canvas_api._request_with_retry")
    def test_happy_path_with_prerequisite(
        self, mock_rwr, mock_create, mock_add, mock_publish
    ):
        cfg = _mock_config()
        mock_create.return_value = {"id": 10, "name": "Module 2"}
        item_returns = [
            {"id": 101}, {"id": 102}, {"id": 103}
        ]
        mock_add.side_effect = item_returns

        put_resp = MagicMock()
        put_resp.status_code = 200
        mock_rwr.return_value = put_resp

        result = canvas_api.create_locked_module(
            cfg, "Module 2", self._items(),
            prerequisite_module_id=9, publish=True
        )

        # create_module called once
        mock_create.assert_called_once_with(
            cfg, "Module 2", position=None,
            require_sequential_progress=False, publish=False
        )

        # add_module_item called 3 times in order with correct positions
        self.assertEqual(mock_add.call_count, 3)
        calls = mock_add.call_args_list
        self.assertEqual(calls[0].args[3], "Page")
        self.assertEqual(calls[0].kwargs["position"], 1)
        self.assertEqual(calls[1].kwargs["position"], 2)
        self.assertEqual(calls[2].kwargs["position"], 3)
        self.assertEqual(calls[2].args[3], "Assignment")

        # prerequisite PUT + 3 completion PUTs = 4 total _request_with_retry calls
        rwr_calls = mock_rwr.call_args_list
        # first PUT is the prerequisite
        prereq_url = f"{cfg['course_url']}/modules/10"
        self.assertEqual(rwr_calls[0].args[1], prereq_url)
        prereq_payload = rwr_calls[0].kwargs["json"]
        self.assertIn(9, prereq_payload["module"]["prerequisite_module_ids"])

        # completion PUTs for each item
        completion_urls = [c.args[1] for c in rwr_calls[1:]]
        for item_id in [101, 102, 103]:
            self.assertIn(
                f"{cfg['course_url']}/modules/10/items/{item_id}",
                completion_urls,
            )

        # publish_module called
        mock_publish.assert_called_once_with(cfg, 10)

        # return dict shape
        self.assertEqual(result["module"]["id"], 10)
        self.assertEqual(result["item_ids"], [101, 102, 103])
        self.assertEqual(result["failed_items"], [])

    @patch("canvas_api.publish_module")
    @patch("canvas_api.add_module_item")
    @patch("canvas_api.create_module")
    @patch("canvas_api._request_with_retry")
    def test_one_item_fails(self, mock_rwr, mock_create, mock_add, mock_publish):
        cfg = _mock_config()
        mock_create.return_value = {"id": 20, "name": "Module 3"}
        # second item fails
        mock_add.side_effect = [{"id": 201}, None, {"id": 203}]

        put_resp = MagicMock()
        put_resp.status_code = 200
        mock_rwr.return_value = put_resp

        items = self._items()
        result = canvas_api.create_locked_module(cfg, "Module 3", items, publish=False)

        self.assertIsNotNone(result)
        self.assertIn("Lecture", result["failed_items"])
        self.assertIn(201, result["item_ids"])
        self.assertIn(203, result["item_ids"])
        self.assertNotIn(None, result["item_ids"])
        # No exception raised
        mock_publish.assert_not_called()

    @patch("canvas_api.publish_module")
    @patch("canvas_api.add_module_item")
    @patch("canvas_api.create_module")
    @patch("canvas_api._request_with_retry")
    def test_no_prerequisite_skips_prereq_put(
        self, mock_rwr, mock_create, mock_add, mock_publish
    ):
        cfg = _mock_config()
        mock_create.return_value = {"id": 30, "name": "Module 1"}
        items = [{"title": "Intro", "type": "Page", "ref": "intro",
                  "completion": None}]
        mock_add.return_value = {"id": 301}

        put_resp = MagicMock()
        put_resp.status_code = 200
        mock_rwr.return_value = put_resp

        canvas_api.create_locked_module(
            cfg, "Module 1", items, prerequisite_module_id=None, publish=False
        )

        # No calls to _request_with_retry (no prereq + no completion)
        mock_rwr.assert_not_called()

    @patch("canvas_api.publish_module")
    @patch("canvas_api.add_module_item")
    @patch("canvas_api.create_module")
    @patch("canvas_api._request_with_retry")
    def test_create_module_failure_returns_none(
        self, mock_rwr, mock_create, mock_add, mock_publish
    ):
        mock_create.return_value = None
        result = canvas_api.create_locked_module(
            _mock_config(), "Bad Module", [], publish=True
        )
        self.assertIsNone(result)
        mock_add.assert_not_called()
        mock_publish.assert_not_called()
