"""
Tests for notes API.
"""

from io import BytesIO
from unittest.mock import MagicMock, Mock, patch

import pytest

from getbiji_box import GetBijiClient
from getbiji_box.exceptions import UploadError, ValidationError
from getbiji_box.models import ChildrenCountResult, Note, NoteListResult


class TestNotesAPI:
    """Test cases for NotesAPI class."""

    def test_notes_property(self):
        """Test getting notes API from client."""
        client = GetBijiClient(token="test_token")
        notes = client.notes
        assert notes is not None

    @patch("getbiji_box.client.GetBijiClient.post")
    def test_create_simple_note(self, mock_post):
        """Test creating a simple note."""
        mock_response = Mock()
        mock_response.json.return_value = {
            "h": {"c": 0},
            "c": {
                "id": "note123",
                "title": "Test Note",
                "content": "Test content",
                "note_type": "plain_text",
                "entry_type": "manual",
                "source": "web",
                "tags": [],
                "attachments": [],
                "is_ai_generated": False,
                "created_at": "2024-01-01T00:00:00Z",
                "updated_at": "2024-01-01T00:00:00Z",
            },
        }
        mock_post.return_value = mock_response

        client = GetBijiClient(token="test_token")
        note = client.notes.create_simple_note(
            content="Test content",
            title="Test Note",
            tags=["test"],
        )

        assert note.id == "note123"
        assert note.content == "Test content"

    def test_create_simple_note_empty_content(self):
        """Test creating note with empty content raises ValidationError."""
        client = GetBijiClient(token="test_token")

        with pytest.raises(ValidationError) as exc_info:
            client.notes.create_simple_note(content="")

        assert "cannot be empty" in str(exc_info.value).lower()

    @patch("getbiji_box.client.GetBijiClient.get")
    def test_list_notes(self, mock_get):
        """Test listing notes."""
        mock_get.return_value = {
            "h": {"c": 0},
            "c": {
                "list": [
                    {
                        "id": "note1",
                        "title": "Note 1",
                        "content": "Content 1",
                        "tags": [],
                        "attachments": [],
                    },
                    {
                        "id": "note2",
                        "title": "Note 2",
                        "content": "Content 2",
                        "tags": [],
                        "attachments": [],
                    },
                ],
                "total_items": 2,
                "has_more": False,
                "limit": 10,
            },
        }

        client = GetBijiClient(token="test_token")
        result = client.notes.list_notes(limit=10)

        assert len(result.list) == 2
        assert result.total_items == 2
        assert result.has_more is False

    @patch("getbiji_box.client.GetBijiClient.get")
    def test_get_note(self, mock_get):
        """Test getting a note by ID."""
        mock_get.return_value = {
            "h": {"c": 0},
            "c": {
                "id": "note123",
                "title": "Test Note",
                "content": "Test content",
                "tags": [],
                "attachments": [],
            },
        }

        client = GetBijiClient(token="test_token")
        note = client.notes.get_note("note123")

        assert note.id == "note123"
        assert note.title == "Test Note"

    def test_get_note_empty_id(self):
        """Test getting note with empty ID raises ValidationError."""
        client = GetBijiClient(token="test_token")

        with pytest.raises(ValidationError) as exc_info:
            client.notes.get_note("")

        assert "cannot be empty" in str(exc_info.value).lower()

    @patch("getbiji_box.client.GetBijiClient.get")
    def test_get_link_note_content(self, mock_get):
        """Test getting link note content."""
        mock_get.return_value = {
            "h": {"c": 0},
            "c": {
                "title": "AI Title",
                "web_title": "Web Title",
                "content": "<html>...</html>",
                "url": "https://example.com",
                "has_content": True,
            },
        }

        client = GetBijiClient(token="test_token")
        detail = client.notes.get_link_note_content("note123")

        assert detail.title == "AI Title"
        assert detail.url == "https://example.com"

    def test_get_link_note_content_empty_id(self):
        """Test getting link note content with empty ID raises ValidationError."""
        client = GetBijiClient(token="test_token")

        with pytest.raises(ValidationError):
            client.notes.get_link_note_content("")

    @patch("getbiji_box.client.GetBijiClient.post")
    @patch("getbiji_box.notes.calculate_md5")
    @patch("getbiji_box.notes.calculate_file_size")
    @patch("getbiji_box.notes.validate_audio_file")
    @patch("os.path.exists")
    @patch("builtins.open", new_callable=MagicMock)
    def test_create_audio_note(
        self,
        mock_open,
        mock_exists,
        mock_validate,
        mock_size,
        mock_md5,
        mock_post,
    ):
        """Test creating audio note - basic test."""
        # This is a simplified test - full test would need more mocking
        mock_exists.return_value = True
        mock_validate.return_value = True
        mock_size.return_value = 1024000
        mock_md5.return_value = "base64md5hash"

        # Mock file content
        mock_file = MagicMock()
        mock_file.__enter__ = Mock(return_value=mock_file)
        mock_file.__exit__ = Mock(return_value=False)
        mock_file.read.return_value = b"audio data"
        mock_open.return_value = mock_file

        client = GetBijiClient(token="test_token")
        # Full test would need to mock all three steps
        # For now, just verify the method exists
        assert hasattr(client.notes, "create_audio_note")

    @patch("getbiji_box.client.GetBijiClient.put")
    def test_edit_note(self, mock_put):
        """Test editing a note."""
        mock_put.return_value = {
            "h": {"c": 0},
            "c": {
                "id": "note123",
                "note_id": "note123",
                "title": "Updated Title",
                "content": "Updated content",
                "note_type": "plain_text",
                "version": 2,
                "display_status": 1,
                "is_ai_generated": False,
            },
        }

        client = GetBijiClient(token="test_token")
        note = client.notes.edit_note(
            note_id="note123",
            title="Updated Title",
            content="Updated content",
            json_content='{"type":"doc","content":[]}',
            version=1,
        )

        assert note.id == "note123"
        assert note.version == 2
        assert note.display_status == 1

    def test_edit_note_empty_id(self):
        """Test editing note with empty ID raises ValidationError."""
        client = GetBijiClient(token="test_token")

        with pytest.raises(ValidationError):
            client.notes.edit_note(
                note_id="",
                title="T",
                content="C",
                json_content="{}",
                version=1,
            )

    def test_edit_note_empty_content(self):
        """Test editing note with empty content raises ValidationError."""
        client = GetBijiClient(token="test_token")

        with pytest.raises(ValidationError):
            client.notes.edit_note(
                note_id="note123",
                title="T",
                content="",
                json_content="{}",
                version=1,
            )

    @patch("getbiji_box.client.GetBijiClient.get")
    def test_get_note_in_topic(self, mock_get):
        """Test getting note within a knowledge base."""
        mock_get.return_value = {
            "h": {"c": 0},
            "c": {
                "id": "note123",
                "title": "Note in KB",
                "content": "Content",
                "source": "knowledge",
                "is_in_topic": True,
                "topic_id": "2732516",
            },
        }

        client = GetBijiClient(token="test_token")
        note = client.notes.get_note_in_topic("2732516", "note123")

        assert note.id == "note123"
        assert note.source == "knowledge"
        assert note.is_in_topic is True

    def test_get_note_in_topic_empty_ids(self):
        """Test get_note_in_topic with empty IDs raises ValidationError."""
        client = GetBijiClient(token="test_token")

        with pytest.raises(ValidationError):
            client.notes.get_note_in_topic("", "note123")

        with pytest.raises(ValidationError):
            client.notes.get_note_in_topic("456", "")

    @patch("getbiji_box.client.GetBijiClient.get")
    def test_get_audio_original(self, mock_get):
        """Test getting audio note original transcript."""
        mock_get.return_value = {
            "h": {"c": 0},
            "c": {
                "content": "Original transcript text...",
            },
        }

        client = GetBijiClient(token="test_token")
        result = client.notes.get_audio_original("2732516", "note123")

        assert "content" in result

    def test_get_audio_original_empty_ids(self):
        """Test get_audio_original with empty IDs raises ValidationError."""
        client = GetBijiClient(token="test_token")

        with pytest.raises(ValidationError):
            client.notes.get_audio_original("", "note123")

    @patch("getbiji_box.client.GetBijiClient.get")
    def test_get_children_count(self, mock_get):
        """Test getting children count for a note."""
        mock_get.return_value = {
            "h": {"c": 0},
            "c": {
                "total": 5,
                "can_add_child_note": True,
                "max_child_note_count": 30,
            },
        }

        client = GetBijiClient(token="test_token")
        result = client.notes.get_children_count("note123")

        assert result.total == 5
        assert result.can_add_child_note is True
        assert result.max_child_note_count == 30

    def test_get_children_count_empty_id(self):
        """Test get_children_count with empty ID raises ValidationError."""
        client = GetBijiClient(token="test_token")

        with pytest.raises(ValidationError):
            client.notes.get_children_count("")

    @patch("getbiji_box.client.GetBijiClient.get")
    def test_get_generating_count(self, mock_get):
        """Test getting generating notes count."""
        mock_get.return_value = {
            "h": {"c": 0},
            "c": {"count": 2},
        }

        client = GetBijiClient(token="test_token")
        count = client.notes.get_generating_count()

        assert count == 2

    @patch("getbiji_box.client.GetBijiClient.get")
    def test_get_notes_count(self, mock_get):
        """Test getting total notes count."""
        mock_get.return_value = {
            "h": {"c": 0},
            "c": {"count": 42},
        }

        client = GetBijiClient(token="test_token")
        count = client.notes.get_notes_count()

        assert count == 42

    @patch("getbiji_box.client.GetBijiClient.get")
    def test_get_export_options(self, mock_get):
        """Test getting export options."""
        mock_get.return_value = {
            "h": {"c": 0},
            "c": {"options": ["markdown", "pdf"]},
        }

        client = GetBijiClient(token="test_token")
        result = client.notes.get_export_options()

        assert "options" in result

    @patch("getbiji_box.client.GetBijiClient.post")
    def test_create_link_note(self, mock_post):
        """Test creating a link note with streaming response."""
        import json

        events = [
            {"msg_type": -1, "data": {"note_id": "ln1"}},
            {
                "msg_type": 6,
                "data": [{"msg": "browsing", "processName": "p", "processStatus": "s"}],
            },
            {"msg_type": -2, "data": {"id": "ln1", "title": "AI Title", "content": "AI Content"}},
        ]

        mock_response = Mock()
        mock_response.stream = True
        chunks = [f"data: {json.dumps(e)}\n\n" for e in events]
        mock_response.iter_content = Mock(return_value=iter(chunks))
        mock_post.return_value = mock_response

        client = GetBijiClient(token="test_token")
        progress_data = []

        def cb(msg_type, data):
            progress_data.append((msg_type, data))

        note = client.notes.create_link_note(
            url="https://example.com/article",
            callback=cb,
        )

        assert note.id == "ln1"
        assert note.title == "AI Title"
        assert len(progress_data) > 0

    def test_create_link_note_empty_url(self):
        """Test creating link note with empty URL raises ValidationError."""
        client = GetBijiClient(token="test_token")
        with pytest.raises(ValidationError):
            client.notes.create_link_note(url="")

    @patch("getbiji_box.client.GetBijiClient.post")
    def test_create_link_note_no_final_note(self, mock_post):
        """Test link note creation when stream has no final note."""
        import json

        events = [{"msg_type": -1, "data": {"note_id": "ln1"}}]

        mock_response = Mock()
        mock_response.stream = True
        chunks = [f"data: {json.dumps(e)}\n\n" for e in events]
        mock_response.iter_content = Mock(return_value=iter(chunks))
        mock_post.return_value = mock_response

        client = GetBijiClient(token="test_token")
        with pytest.raises(UploadError, match="Failed to create link note"):
            client.notes.create_link_note(url="https://example.com")

    @patch("getbiji_box.client.GetBijiClient.post")
    def test_create_link_note_with_prompt_template(self, mock_post):
        """Test creating link note with prompt template."""
        import json

        events = [
            {"msg_type": -2, "data": {"id": "ln2", "title": "T", "content": "C"}},
        ]

        mock_response = Mock()
        mock_response.stream = True
        chunks = [f"data: {json.dumps(e)}\n\n" for e in events]
        mock_response.iter_content = Mock(return_value=iter(chunks))
        mock_post.return_value = mock_response

        client = GetBijiClient(token="test_token")
        note = client.notes.create_link_note(
            url="https://example.com",
            prompt_template_id="custom",
        )
        assert note.id == "ln2"

    @patch("getbiji_box.notes.requests.put")
    @patch("getbiji_box.client.GetBijiClient.post")
    @patch("getbiji_box.notes.calculate_md5")
    @patch("getbiji_box.notes.calculate_file_size")
    @patch("getbiji_box.notes.get_audio_duration_ms")
    @patch("getbiji_box.notes.validate_audio_file")
    @patch("os.path.exists")
    @patch("builtins.open", new_callable=MagicMock)
    def test_create_audio_note_full(
        self,
        mock_open,
        mock_exists,
        mock_validate,
        mock_duration,
        mock_size,
        mock_md5,
        mock_post,
        mock_oss_put,
    ):
        """Test full audio note creation flow."""
        import json

        mock_exists.return_value = True
        mock_validate.return_value = True
        mock_size.return_value = 1024000
        mock_md5.return_value = "base64md5hash"
        mock_duration.return_value = 60000

        mock_file = MagicMock()
        mock_file.__enter__ = Mock(return_value=mock_file)
        mock_file.__exit__ = Mock(return_value=False)
        mock_file.read.return_value = b"audio data"
        mock_open.return_value = mock_file

        # Step 1: token response
        token_response = Mock()
        token_response.json.return_value = {
            "h": {"c": 0},
            "c": {
                "is_uploaded": False,
                "file_id": "file123",
                "token_info": {
                    "object_key": "key",
                    "put_sign_url": "https://oss.example.com/upload",
                    "put_content_type": "audio/mp3",
                    "put_md5": "md5",
                    "get_url": "https://oss.example.com/file.mp3",
                },
            },
        }

        # Step 3: stream response
        events = [
            {"msg_type": -1, "data": {"note_id": "an1"}},
            {"msg_type": -2, "data": {"id": "an1", "title": "Audio Note", "content": "Transcript"}},
        ]
        stream_response = Mock()
        stream_response.stream = True
        chunks = [f"data: {json.dumps(e)}\n\n" for e in events]
        stream_response.iter_content = Mock(return_value=iter(chunks))

        mock_post.side_effect = [token_response, stream_response]

        # Step 2: OSS upload
        mock_oss_response = Mock()
        mock_oss_response.status_code = 200
        mock_oss_put.return_value = mock_oss_response

        client = GetBijiClient(token="test_token")
        note = client.notes.create_audio_note(audio_path="/path/to/audio.mp3")

        assert note.id == "an1"
        assert note.title == "Audio Note"

    def test_create_audio_note_file_not_found(self):
        """Test audio note with nonexistent file."""
        client = GetBijiClient(token="test_token")
        with pytest.raises(ValidationError, match="not found"):
            client.notes.create_audio_note(audio_path="/nonexistent/file.mp3")

    @patch("os.path.exists")
    def test_create_audio_note_invalid_file(self, mock_exists):
        """Test audio note with invalid audio file."""
        mock_exists.return_value = True
        client = GetBijiClient(token="test_token")
        with patch("getbiji_box.notes.validate_audio_file", return_value=False):
            with pytest.raises(ValidationError, match="Invalid audio"):
                client.notes.create_audio_note(audio_path="/path/to/file.txt")

    @patch("getbiji_box.client.GetBijiClient.get")
    def test_list_notes_with_since_id(self, mock_get):
        """Test listing notes with since_id pagination."""
        mock_get.return_value = {
            "h": {"c": 0},
            "c": {"list": [], "total_items": 0, "has_more": False, "limit": 10},
        }

        client = GetBijiClient(token="test_token")
        client.notes.list_notes(limit=10, since_id="last_id")

        call_kwargs = mock_get.call_args
        assert call_kwargs.kwargs["params"]["since_id"] == "last_id"

    @patch("getbiji_box.client.GetBijiClient.get")
    def test_get_audio_note_sections(self, mock_get):
        """Test getting audio note sections."""
        mock_get.return_value = {
            "h": {"c": 0},
            "c": {
                "id": "an1",
                "title": "Audio",
                "content": "[00:01:00](https://getnotes.seek:60000) **Chapter 1**\n\nSome text",
                "note_type": "local_audio",
            },
        }

        client = GetBijiClient(token="test_token")
        note = client.notes.get_note("an1")
        sections = client.notes.get_audio_note_sections(note)

        assert len(sections) == 1
        assert sections[0]["title"] == "Chapter 1"
        assert sections[0]["milliseconds"] == 60000

    def test_get_audio_note_sections_wrong_type(self):
        """Test get_audio_note_sections with non-audio note."""
        from getbiji_box.models import Note

        note = Note(id="n1", title="T", content="C", note_type="plain_text")
        client = GetBijiClient(token="test_token")
        sections = client.notes.get_audio_note_sections(note)
        assert sections == []

    @patch("getbiji_box.client.GetBijiClient.post")
    @patch("getbiji_box.client.GetBijiClient.get")
    def test_add_note_to_knowledge_base(self, mock_get, mock_post):
        """Test adding a note to a knowledge base."""
        mock_get.return_value = {
            "h": {"c": 0},
            "c": {
                "id": "n1",
                "title": "Original",
                "content": "Content",
                "json_content": "{}",
                "entry_type": "manual",
                "note_type": "plain_text",
            },
        }

        mock_response = Mock()
        mock_response.json.return_value = {
            "h": {"c": 0},
            "c": {
                "id": "n2",
                "title": "Original",
                "content": "Content",
                "source": "knowledge",
            },
        }
        mock_post.return_value = mock_response

        client = GetBijiClient(token="test_token")
        note = client.notes.add_note_to_knowledge_base(note_id="n1", topic_id="35")

        assert note.id == "n2"
        assert note.source == "knowledge"

    def test_add_note_to_knowledge_base_empty_ids(self):
        """Test add_note_to_knowledge_base with empty IDs."""
        client = GetBijiClient(token="test_token")

        with pytest.raises(ValidationError):
            client.notes.add_note_to_knowledge_base(note_id="", topic_id="35")

        with pytest.raises(ValidationError):
            client.notes.add_note_to_knowledge_base(note_id="n1", topic_id="")

    @patch("getbiji_box.client.GetBijiClient.put")
    def test_edit_note_with_tags(self, mock_put):
        """Test editing a note with tags."""
        mock_put.return_value = {
            "h": {"c": 0},
            "c": {"id": "n1", "title": "T", "content": "C", "version": 3},
        }

        client = GetBijiClient(token="test_token")
        note = client.notes.edit_note(
            note_id="n1",
            title="T",
            content="C",
            json_content="{}",
            version=2,
            tags=[{"name": "tag1", "type": "text", "tag_type": "text"}],
        )
        assert note.version == 3

    @patch("getbiji_box.client.GetBijiClient.put")
    def test_edit_note_with_attachments(self, mock_put):
        """Test editing a note with attachments."""
        mock_put.return_value = {
            "h": {"c": 0},
            "c": {"id": "n1", "title": "T", "content": "C", "version": 2},
        }

        client = GetBijiClient(token="test_token")
        note = client.notes.edit_note(
            note_id="n1",
            title="T",
            content="C",
            json_content="{}",
            version=1,
            attachments=[{"type": "link", "url": "https://example.com"}],
        )
        assert note.version == 2

    @patch("getbiji_box.notes.requests.put")
    @patch("getbiji_box.client.GetBijiClient.post")
    @patch("getbiji_box.notes.calculate_md5")
    @patch("getbiji_box.notes.calculate_file_size")
    @patch("getbiji_box.notes.get_audio_duration_ms")
    @patch("getbiji_box.notes.validate_audio_file")
    @patch("os.path.exists")
    def test_create_audio_note_already_uploaded(
        self,
        mock_exists,
        mock_validate,
        mock_duration,
        mock_size,
        mock_md5,
        mock_post,
        mock_oss_put,
    ):
        """Test audio note when file is already uploaded (instant upload)."""
        import json

        mock_exists.return_value = True
        mock_validate.return_value = True
        mock_size.return_value = 1024
        mock_md5.return_value = "md5"
        mock_duration.return_value = 5000

        token_response = Mock()
        token_response.json.return_value = {
            "h": {"c": 0},
            "c": {
                "is_uploaded": True,
                "file_id": "file123",
                "image": {"path": "https://oss.example.com/existing.mp3"},
            },
        }

        events = [
            {"msg_type": -2, "data": {"id": "an2", "title": "Audio", "content": "C"}},
        ]
        stream_response = Mock()
        stream_response.stream = True
        chunks = [f"data: {json.dumps(e)}\n\n" for e in events]
        stream_response.iter_content = Mock(return_value=iter(chunks))

        mock_post.side_effect = [token_response, stream_response]

        client = GetBijiClient(token="test_token")
        note = client.notes.create_audio_note(audio_path="/path/audio.mp3")
        assert note.id == "an2"

    @patch("getbiji_box.notes.requests.put")
    @patch("getbiji_box.client.GetBijiClient.post")
    @patch("getbiji_box.notes.calculate_md5")
    @patch("getbiji_box.notes.calculate_file_size")
    @patch("getbiji_box.notes.get_audio_duration_ms")
    @patch("getbiji_box.notes.validate_audio_file")
    @patch("os.path.exists")
    @patch("builtins.open", new_callable=MagicMock)
    def test_create_audio_note_oss_upload_fails(
        self,
        mock_open,
        mock_exists,
        mock_validate,
        mock_duration,
        mock_size,
        mock_md5,
        mock_post,
        mock_oss_put,
    ):
        """Test audio note creation when OSS upload fails."""
        mock_exists.return_value = True
        mock_validate.return_value = True
        mock_size.return_value = 1024
        mock_md5.return_value = "md5"
        mock_duration.return_value = 5000

        mock_file = MagicMock()
        mock_file.__enter__ = Mock(return_value=mock_file)
        mock_file.__exit__ = Mock(return_value=False)
        mock_file.read.return_value = b"data"
        mock_open.return_value = mock_file

        token_response = Mock()
        token_response.json.return_value = {
            "h": {"c": 0},
            "c": {
                "is_uploaded": False,
                "file_id": "file123",
                "token_info": {
                    "object_key": "key",
                    "put_sign_url": "https://oss.example.com/upload",
                    "put_content_type": "audio/mp3",
                    "put_md5": "md5",
                    "get_url": "https://oss.example.com/file.mp3",
                },
            },
        }
        mock_post.return_value = token_response

        mock_oss_response = Mock()
        mock_oss_response.status_code = 500
        mock_oss_put.return_value = mock_oss_response

        client = GetBijiClient(token="test_token")
        with pytest.raises(UploadError, match="OSS upload failed"):
            client.notes.create_audio_note(audio_path="/path/audio.mp3")

    @patch("getbiji_box.client.GetBijiClient.post")
    @patch("getbiji_box.notes.calculate_md5")
    @patch("getbiji_box.notes.calculate_file_size")
    @patch("getbiji_box.notes.get_audio_duration_ms")
    @patch("getbiji_box.notes.validate_audio_file")
    @patch("os.path.exists")
    def test_create_audio_note_stream_no_note(
        self,
        mock_exists,
        mock_validate,
        mock_duration,
        mock_size,
        mock_md5,
        mock_post,
    ):
        """Test audio note when stream returns no final note."""
        import json

        mock_exists.return_value = True
        mock_validate.return_value = True
        mock_size.return_value = 1024
        mock_md5.return_value = "md5"
        mock_duration.return_value = 5000

        token_response = Mock()
        token_response.json.return_value = {
            "h": {"c": 0},
            "c": {
                "is_uploaded": True,
                "file_id": "file123",
                "image": {"path": "https://oss.example.com/existing.mp3"},
            },
        }

        # Stream with no -2 event
        events = [{"msg_type": -1, "data": {"note_id": "an3"}}]
        stream_response = Mock()
        stream_response.stream = True
        chunks = [f"data: {json.dumps(e)}\n\n" for e in events]
        stream_response.iter_content = Mock(return_value=iter(chunks))

        mock_post.side_effect = [token_response, stream_response]

        client = GetBijiClient(token="test_token")
        with pytest.raises(UploadError, match="Failed to create audio note"):
            client.notes.create_audio_note(audio_path="/path/audio.mp3")

    @patch("getbiji_box.client.GetBijiClient.post")
    @patch("getbiji_box.notes.calculate_md5")
    @patch("getbiji_box.notes.calculate_file_size")
    @patch("getbiji_box.notes.get_audio_duration_ms")
    @patch("getbiji_box.notes.validate_audio_file")
    @patch("os.path.exists")
    def test_create_audio_note_fallback_url_with_token_info(
        self,
        mock_exists,
        mock_validate,
        mock_duration,
        mock_size,
        mock_md5,
        mock_post,
    ):
        """Test audio note URL fallback when is_uploaded=True but no image_path."""
        import json

        mock_exists.return_value = True
        mock_validate.return_value = True
        mock_size.return_value = 1024
        mock_md5.return_value = "md5"
        mock_duration.return_value = 5000

        token_response = Mock()
        token_response.json.return_value = {
            "h": {"c": 0},
            "c": {
                "is_uploaded": True,
                "file_id": "file123",
                "image": {},
                "token_info": {
                    "object_key": "key",
                    "put_sign_url": "https://oss.example.com/upload",
                    "put_content_type": "audio/mp3",
                    "put_md5": "md5",
                    "get_url": "https://oss.example.com/fallback.mp3",
                },
            },
        }

        events = [
            {"msg_type": -2, "data": {"id": "an_fb", "title": "FB", "content": "C"}},
        ]
        stream_response = Mock()
        stream_response.stream = True
        chunks = [f"data: {json.dumps(e)}\n\n" for e in events]
        stream_response.iter_content = Mock(return_value=iter(chunks))

        mock_post.side_effect = [token_response, stream_response]

        client = GetBijiClient(token="test_token")
        note = client.notes.create_audio_note(audio_path="/path/audio.mp3")
        assert note.id == "an_fb"

    @patch("getbiji_box.client.GetBijiClient.post")
    @patch("getbiji_box.notes.calculate_md5")
    @patch("getbiji_box.notes.calculate_file_size")
    @patch("getbiji_box.notes.get_audio_duration_ms")
    @patch("getbiji_box.notes.validate_audio_file")
    @patch("os.path.exists")
    def test_create_audio_note_fallback_url_no_token_info(
        self,
        mock_exists,
        mock_validate,
        mock_duration,
        mock_size,
        mock_md5,
        mock_post,
    ):
        """Test audio note URL fallback to file:// when no token_info and no image_path."""
        import json

        mock_exists.return_value = True
        mock_validate.return_value = True
        mock_size.return_value = 1024
        mock_md5.return_value = "md5"
        mock_duration.return_value = 5000

        token_response = Mock()
        token_response.json.return_value = {
            "h": {"c": 0},
            "c": {
                "is_uploaded": True,
                "file_id": "file123",
                "image": {},
            },
        }

        events = [
            {"msg_type": -2, "data": {"id": "an_nf", "title": "NF", "content": "C"}},
        ]
        stream_response = Mock()
        stream_response.stream = True
        chunks = [f"data: {json.dumps(e)}\n\n" for e in events]
        stream_response.iter_content = Mock(return_value=iter(chunks))

        mock_post.side_effect = [token_response, stream_response]

        client = GetBijiClient(token="test_token")
        note = client.notes.create_audio_note(audio_path="/path/audio.mp3")
        assert note.id == "an_nf"

    @patch("getbiji_box.client.GetBijiClient.post")
    @patch("getbiji_box.notes.calculate_md5")
    @patch("getbiji_box.notes.calculate_file_size")
    @patch("getbiji_box.notes.get_audio_duration_ms")
    @patch("getbiji_box.notes.validate_audio_file")
    @patch("os.path.exists")
    def test_create_audio_note_with_prompt_template_and_callback(
        self,
        mock_exists,
        mock_validate,
        mock_duration,
        mock_size,
        mock_md5,
        mock_post,
    ):
        """Test audio note with prompt_template_id and callback for init/-6 events."""
        import json

        mock_exists.return_value = True
        mock_validate.return_value = True
        mock_size.return_value = 1024
        mock_md5.return_value = "md5"
        mock_duration.return_value = 5000

        token_response = Mock()
        token_response.json.return_value = {
            "h": {"c": 0},
            "c": {
                "is_uploaded": True,
                "file_id": "file123",
                "image": {"path": "https://oss.example.com/existing.mp3"},
            },
        }

        events = [
            {"msg_type": -1, "data": {"note_id": "an_cb"}},
            {"msg_type": -6, "data": [{"title": "Ch1"}]},
            {"msg_type": -2, "data": {"id": "an_cb", "title": "CB", "content": "C"}},
        ]
        stream_response = Mock()
        stream_response.stream = True
        chunks = [f"data: {json.dumps(e)}\n\n" for e in events]
        stream_response.iter_content = Mock(return_value=iter(chunks))

        mock_post.side_effect = [token_response, stream_response]

        progress_data = []

        def cb(msg_type, data):
            progress_data.append((msg_type, data))

        client = GetBijiClient(token="test_token")
        note = client.notes.create_audio_note(
            audio_path="/path/audio.mp3",
            prompt_template_id="custom_prompt",
            callback=cb,
        )
        assert note.id == "an_cb"
        # Should have received -1 (init) and -6 (chapter) callbacks
        assert len(progress_data) == 2
        assert progress_data[0][0] == -1
        assert progress_data[1][0] == -6


if __name__ == "__main__":
    pytest.main([__file__, "-v"])
