"""Tests for QB1: upload-path timeout kwargs in canvas_api.py.

Asserts that every raw `requests.*` call in the upload, folder, and media
helper paths passes an explicit `timeout=` kwarg.  These calls bypass
`_request_with_retry` and previously had no timeout, meaning a stalled
socket would hang forever.
"""

import io
import os
import sys
import unittest
from unittest.mock import MagicMock, call, 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"},
    }


def _make_resp(status=200, body=None, headers=None):
    resp = MagicMock()
    resp.status_code = status
    resp.headers = headers or {}
    resp.json.return_value = body or {}
    return resp


# ---------------------------------------------------------------------------
# upload_file — 3-step process
# ---------------------------------------------------------------------------

class TestUploadFileTimeouts(unittest.TestCase):
    """Each requests call in upload_file must carry a timeout kwarg."""

    def _make_tmpfile(self, tmp_path="/tmp/canvas_api_test_upload.html"):
        """Write a small temp file and return its path."""
        with open(tmp_path, "w") as fh:
            fh.write("<p>test</p>")
        return tmp_path

    @patch("canvas_api.requests.post")
    def test_step1_post_has_timeout(self, mock_post):
        """Step 1 (request upload URL): requests.post must include timeout=30."""
        # Step 1 returns 200 with upload_url; step 2 returns 200 with id
        step1_resp = _make_resp(200, {
            "upload_url": "https://canvas.test.edu/upload",
            "upload_params": {},
        })
        step2_resp = _make_resp(200, {"id": 42})
        mock_post.side_effect = [step1_resp, step2_resp]

        tmp = self._make_tmpfile()
        try:
            canvas_api.upload_file(_mock_config(), tmp, folder_id=1)
        except Exception:
            pass  # we only care about the calls

        # First call = step 1
        first_call_kwargs = mock_post.call_args_list[0][1]
        self.assertIn("timeout", first_call_kwargs,
                      "upload_file step 1 POST must pass timeout=")
        self.assertEqual(first_call_kwargs["timeout"], 30)

    @patch("canvas_api.requests.post")
    def test_step2_post_has_timeout(self, mock_post):
        """Step 2 (file upload POST): requests.post must include timeout=120."""
        step1_resp = _make_resp(200, {
            "upload_url": "https://canvas.test.edu/upload",
            "upload_params": {},
        })
        step2_resp = _make_resp(200, {"id": 42})
        mock_post.side_effect = [step1_resp, step2_resp]

        tmp = self._make_tmpfile()
        try:
            canvas_api.upload_file(_mock_config(), tmp, folder_id=1)
        except Exception:
            pass

        # Second call = step 2
        second_call_kwargs = mock_post.call_args_list[1][1]
        self.assertIn("timeout", second_call_kwargs,
                      "upload_file step 2 POST must pass timeout=")
        self.assertEqual(second_call_kwargs["timeout"], 120)

    @patch("canvas_api.requests.get")
    @patch("canvas_api.requests.post")
    def test_step3_confirm_get_has_timeout(self, mock_post, mock_get):
        """Step 3 (confirm redirect GET): requests.get must include timeout=30."""
        step1_resp = _make_resp(200, {
            "upload_url": "https://canvas.test.edu/upload",
            "upload_params": {},
        })
        # Step 2 returns 301 redirect to a canvas.test.edu confirm URL
        step2_resp = _make_resp(301, headers={"Location": "https://canvas.test.edu/api/v1/confirm"})
        mock_post.side_effect = [step1_resp, step2_resp]

        step3_resp = _make_resp(200, {"id": 99})
        mock_get.return_value = step3_resp

        tmp = self._make_tmpfile()
        try:
            canvas_api.upload_file(_mock_config(), tmp, folder_id=1)
        except Exception:
            pass

        # The GET call is step 3
        self.assertTrue(mock_get.called,
                        "upload_file step 3 should call requests.get")
        get_kwargs = mock_get.call_args[1]
        self.assertIn("timeout", get_kwargs,
                      "upload_file step 3 GET must pass timeout=")
        self.assertEqual(get_kwargs["timeout"], 30)


# ---------------------------------------------------------------------------
# get_or_create_folder
# ---------------------------------------------------------------------------

class TestGetOrCreateFolderTimeouts(unittest.TestCase):

    @patch("canvas_api.requests.get")
    def test_folder_get_has_timeout(self, mock_get):
        """Folder existence check GET must include timeout=30."""
        mock_get.return_value = _make_resp(200, [{"id": 7, "name": "images"}])
        canvas_api.get_or_create_folder(_mock_config(), "course files", "images")

        get_kwargs = mock_get.call_args[1]
        self.assertIn("timeout", get_kwargs,
                      "get_or_create_folder GET must pass timeout=")
        self.assertEqual(get_kwargs["timeout"], 30)

    @patch("canvas_api.requests.post")
    @patch("canvas_api.requests.get")
    def test_folder_post_has_timeout(self, mock_get, mock_post):
        """Folder creation POST must include timeout=30."""
        # GET returns 404 so we fall through to POST
        mock_get.return_value = _make_resp(404)
        mock_post.return_value = _make_resp(201, {"id": 8, "name": "images"})

        canvas_api.get_or_create_folder(_mock_config(), "course files", "images")

        post_kwargs = mock_post.call_args[1]
        self.assertIn("timeout", post_kwargs,
                      "get_or_create_folder POST must pass timeout=")
        self.assertEqual(post_kwargs["timeout"], 30)


# ---------------------------------------------------------------------------
# get_media_entry_id
# ---------------------------------------------------------------------------

class TestGetMediaEntryIdTimeout(unittest.TestCase):

    @patch("canvas_api.requests.get")
    def test_get_media_entry_id_has_timeout(self, mock_get):
        """get_media_entry_id GET must include timeout=30."""
        mock_get.return_value = _make_resp(200, {"media_entry_id": "m-abc123"})
        canvas_api.get_media_entry_id(_mock_config(), file_id=555)

        get_kwargs = mock_get.call_args[1]
        self.assertIn("timeout", get_kwargs,
                      "get_media_entry_id GET must pass timeout=")
        self.assertEqual(get_kwargs["timeout"], 30)


# ---------------------------------------------------------------------------
# upload_caption
# ---------------------------------------------------------------------------

class TestUploadCaptionTimeout(unittest.TestCase):

    @patch("canvas_api.requests.put")
    def test_upload_caption_put_has_timeout(self, mock_put):
        """upload_caption PUT must include timeout=30."""
        mock_put.return_value = _make_resp(200)
        canvas_api.upload_caption(_mock_config(), "m-abc123", "WEBVTT\n")

        put_kwargs = mock_put.call_args[1]
        self.assertIn("timeout", put_kwargs,
                      "upload_caption PUT must pass timeout=")
        self.assertEqual(put_kwargs["timeout"], 30)


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