"""
Tests for utility functions.
"""

import os
import tempfile
import time
from unittest.mock import patch

import pytest

from getbiji_box.utils import (
    build_json_content,
    calculate_file_size,
    calculate_md5,
    chunks,
    extract_csrf_token,
    extract_error_message,
    format_json_content,
    generate_request_id,
    get_audio_duration_ms,
    get_file_extension,
    is_token_expired,
    parse_note_id_from_url,
    retry_on_failure,
    safe_get,
    sanitize_filename,
    validate_audio_file,
)


class TestCalculateMd5:
    """Test cases for calculate_md5."""

    def test_basic(self, tmp_path):
        f = tmp_path / "test.bin"
        f.write_bytes(b"hello world")
        result = calculate_md5(str(f))
        assert isinstance(result, str)
        assert len(result) > 0

    def test_empty_file(self, tmp_path):
        f = tmp_path / "empty.bin"
        f.write_bytes(b"")
        result = calculate_md5(str(f))
        assert isinstance(result, str)


class TestCalculateFileSize:
    """Test cases for calculate_file_size."""

    def test_basic(self, tmp_path):
        f = tmp_path / "test.bin"
        f.write_bytes(b"hello")
        assert calculate_file_size(str(f)) == 5

    def test_empty(self, tmp_path):
        f = tmp_path / "empty.bin"
        f.write_bytes(b"")
        assert calculate_file_size(str(f)) == 0


class TestGetAudioDurationMs:
    """Test cases for get_audio_duration_ms."""

    def test_basic(self, tmp_path):
        f = tmp_path / "audio.mp3"
        f.write_bytes(b"x" * 1024)
        result = get_audio_duration_ms(str(f))
        assert isinstance(result, int)
        assert result > 0


class TestValidateAudioFile:
    """Test cases for validate_audio_file."""

    def test_valid_mp3(self, tmp_path):
        f = tmp_path / "audio.mp3"
        f.write_bytes(b"data")
        assert validate_audio_file(str(f)) is True

    def test_valid_wav(self, tmp_path):
        f = tmp_path / "audio.wav"
        f.write_bytes(b"data")
        assert validate_audio_file(str(f)) is True

    def test_valid_m4a(self, tmp_path):
        f = tmp_path / "audio.m4a"
        f.write_bytes(b"data")
        assert validate_audio_file(str(f)) is True

    def test_invalid_ext(self, tmp_path):
        f = tmp_path / "file.txt"
        f.write_bytes(b"data")
        assert validate_audio_file(str(f)) is False

    def test_nonexistent(self):
        assert validate_audio_file("/nonexistent/file.mp3") is False


class TestGetFileExtension:
    """Test cases for get_file_extension."""

    def test_mp3(self):
        assert get_file_extension("audio.mp3") == "mp3"

    def test_uppercase(self):
        assert get_file_extension("audio.MP3") == "mp3"

    def test_no_ext(self):
        assert get_file_extension("noext") == ""


class TestExtractCsrfToken:
    """Test cases for extract_csrf_token."""

    def test_x_csrf_token(self):
        headers = {"x-csrf-token": "abc123"}
        assert extract_csrf_token(headers) == "abc123"

    def test_x_xsrf_token(self):
        headers = {"x-xsrf-token": "def456"}
        assert extract_csrf_token(headers) == "def456"

    def test_csrf_token(self):
        headers = {"csrf-token": "ghi789"}
        assert extract_csrf_token(headers) == "ghi789"

    def test_not_found(self):
        headers = {"content-type": "text/html"}
        assert extract_csrf_token(headers) is None

    def test_empty_value(self):
        headers = {"x-csrf-token": ""}
        assert extract_csrf_token(headers) is None


class TestGenerateRequestId:
    """Test cases for generate_request_id."""

    def test_returns_string(self):
        result = generate_request_id()
        assert isinstance(result, str)
        assert int(result) > 0


class TestFormatJsonContent:
    """Test cases for format_json_content."""

    def test_basic(self):
        import json

        result = format_json_content("hello")
        parsed = json.loads(result)
        assert parsed["type"] == "doc"
        assert parsed["content"][0]["type"] == "paragraph"
        assert parsed["content"][0]["content"][0]["text"] == "hello"

    def test_chinese(self):
        import json

        result = format_json_content("你好世界")
        parsed = json.loads(result)
        assert parsed["content"][0]["content"][0]["text"] == "你好世界"


class TestSanitizeFilename:
    """Test cases for sanitize_filename."""

    def test_basic(self):
        assert sanitize_filename("normal.txt") == "normal.txt"

    def test_invalid_chars(self):
        result = sanitize_filename('file<>:"/\\|?*.txt')
        assert "<" not in result
        assert ">" not in result
        assert ":" not in result
        assert '"' not in result
        assert "\\" not in result
        assert "|" not in result
        assert "?" not in result
        assert "*" not in result

    def test_strips_whitespace(self):
        assert sanitize_filename("  file.txt  ") == "file.txt"


class TestExtractErrorMessage:
    """Test cases for extract_error_message."""

    def test_from_h_section(self):
        data = {"h": {"c": 1, "e": "Token expired"}}
        assert extract_error_message(data) == "Token expired"

    def test_from_c_error(self):
        data = {"c": {"error": "Not found"}}
        assert extract_error_message(data) == "Not found"

    def test_h_without_e(self):
        data = {"h": {"c": 1}}
        assert extract_error_message(data) == "Unknown error"

    def test_unknown(self):
        data = {"something": "else"}
        assert extract_error_message(data) == "Unknown error"

    def test_h_not_dict(self):
        data = {"h": "not a dict"}
        assert extract_error_message(data) == "Unknown error"

    def test_c_without_error(self):
        data = {"c": {"data": "value"}}
        assert extract_error_message(data) == "Unknown error"


class TestIsTokenExpired:
    """Test cases for is_token_expired."""

    def test_expired(self):
        expire_at = int(time.time()) - 100
        assert is_token_expired(expire_at) is True

    def test_not_expired(self):
        expire_at = int(time.time()) + 3600
        assert is_token_expired(expire_at) is False

    def test_within_buffer(self):
        expire_at = int(time.time()) + 200
        assert is_token_expired(expire_at, buffer_seconds=300) is True


class TestParseNoteIdFromUrl:
    """Test cases for parse_note_id_from_url."""

    def test_notes_pattern(self):
        assert parse_note_id_from_url("https://getbiji.com/notes/abc123") == "abc123"

    def test_note_pattern(self):
        assert parse_note_id_from_url("https://getbiji.com/note/def456") == "def456"

    def test_query_param(self):
        assert parse_note_id_from_url("https://getbiji.com?noteId=ghi789") == "ghi789"

    def test_no_match(self):
        assert parse_note_id_from_url("https://example.com/page") is None


class TestBuildJsonContent:
    """Test cases for build_json_content."""

    def test_default_paragraph(self):
        import json

        result = build_json_content("hello")
        parsed = json.loads(result)
        assert parsed["type"] == "doc"
        assert parsed["content"][0]["type"] == "paragraph"
        assert parsed["content"][0]["content"][0]["text"] == "hello"

    def test_custom_type(self):
        import json

        result = build_json_content("heading", content_type="heading")
        parsed = json.loads(result)
        assert parsed["content"][0]["type"] == "heading"

    def test_custom_attrs(self):
        import json

        result = build_json_content("text", indent=2)
        parsed = json.loads(result)
        assert parsed["content"][0]["attrs"]["indent"] == 2


class TestSafeGet:
    """Test cases for safe_get."""

    def test_single_key(self):
        data = {"a": 1}
        assert safe_get(data, "a") == 1

    def test_nested(self):
        data = {"a": {"b": {"c": 3}}}
        assert safe_get(data, "a", "b", "c") == 3

    def test_missing_key(self):
        data = {"a": 1}
        assert safe_get(data, "b", default="missing") == "missing"

    def test_non_dict_intermediate(self):
        data = {"a": "not_a_dict"}
        assert safe_get(data, "a", "b", default=None) is None

    def test_default_none(self):
        data = {}
        assert safe_get(data, "x") is None


class TestChunks:
    """Test cases for chunks."""

    def test_basic(self):
        result = chunks([1, 2, 3, 4, 5], 2)
        assert result == [[1, 2], [3, 4], [5]]

    def test_exact_fit(self):
        result = chunks([1, 2, 3, 4], 2)
        assert result == [[1, 2], [3, 4]]

    def test_empty(self):
        result = chunks([], 3)
        assert result == []

    def test_single_chunk(self):
        result = chunks([1, 2], 5)
        assert result == [[1, 2]]


class TestRetryOnFailure:
    """Test cases for retry_on_failure."""

    def test_success_first_try(self):
        def succeed():
            return "ok"

        wrapped = retry_on_failure(succeed, max_retries=3)
        assert wrapped() == "ok"

    def test_fail_then_succeed(self):
        call_count = {"n": 0}

        def flaky():
            call_count["n"] += 1
            if call_count["n"] < 2:
                raise ValueError("fail")
            return "ok"

        wrapped = retry_on_failure(flaky, max_retries=3, retry_exceptions=(ValueError,))
        with patch("getbiji_box.utils.time.sleep"):
            result = wrapped()
        assert result == "ok"
        assert call_count["n"] == 2

    def test_all_retries_exhausted(self):
        def always_fail():
            raise ValueError("fail")

        wrapped = retry_on_failure(always_fail, max_retries=2, retry_exceptions=(ValueError,))
        with patch("getbiji_box.utils.time.sleep"):
            with pytest.raises(ValueError):
                wrapped()


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