"""
Tests for utility functions

Organized by function category:
- Slug generation tests
- Helper function tests
- Edge cases and security tests
"""

import pytest
from datetime import datetime
from pathlib import Path
from starpunk.utils import (
    # Helper functions
    extract_first_words,
    normalize_slug_text,
    generate_random_suffix,
    # Slug functions
    generate_slug,
    make_slug_unique,
    validate_slug,
    # Content hashing
    calculate_content_hash,
    # Path operations
    generate_note_path,
    ensure_note_directory,
    validate_note_path,
    # File operations
    write_note_file,
    read_note_file,
    delete_note_file,
    # Date/time utilities
    format_rfc822,
    format_iso8601,
    parse_iso8601,
    # Constants
    RANDOM_SUFFIX_LENGTH,
    MAX_SLUG_LENGTH,
    TRASH_DIR_NAME,
)


class TestHelperFunctions:
    """Test helper functions used by slug generation"""

    def test_extract_first_words_default(self):
        """Test extracting first 5 words (default)."""
        text = "Hello world this is a test note content"
        result = extract_first_words(text)
        assert result == "Hello world this is a"

    def test_extract_first_words_custom_count(self):
        """Test extracting custom number of words."""
        text = "One two three four five six seven"
        result = extract_first_words(text, max_words=3)
        assert result == "One two three"

    def test_extract_first_words_fewer_than_max(self):
        """Test extracting when text has fewer words than max."""
        text = "Only three words"
        result = extract_first_words(text, max_words=5)
        assert result == "Only three words"

    def test_extract_first_words_multiple_spaces(self):
        """Test extracting handles multiple spaces correctly."""
        text = "  Multiple   spaces   between   words  "
        result = extract_first_words(text, max_words=3)
        assert result == "Multiple spaces between"

    def test_extract_first_words_empty(self):
        """Test extracting from empty string."""
        text = ""
        result = extract_first_words(text)
        assert result == ""

    def test_normalize_slug_text_basic(self):
        """Test basic text normalization."""
        assert normalize_slug_text("Hello World") == "hello-world"

    def test_normalize_slug_text_special_characters(self):
        """Test normalization removes special characters."""
        assert normalize_slug_text("Testing!@#$%^&*()") == "testing"
        assert normalize_slug_text("Hello... World!!!") == "hello-world"

    def test_normalize_slug_text_multiple_hyphens(self):
        """Test normalization collapses multiple hyphens."""
        assert normalize_slug_text("Hello -- World") == "hello-world"
        assert normalize_slug_text("Test---note") == "test-note"

    def test_normalize_slug_text_leading_trailing_hyphens(self):
        """Test normalization strips leading/trailing hyphens."""
        assert normalize_slug_text("-Hello-") == "hello"
        assert normalize_slug_text("---Test---") == "test"

    def test_normalize_slug_text_numbers(self):
        """Test normalization preserves numbers."""
        assert normalize_slug_text("Test 123 Note") == "test-123-note"
        assert normalize_slug_text("2024-11-18") == "2024-11-18"

    def test_normalize_slug_text_unicode(self):
        """Test normalization handles unicode (strips it)."""
        assert normalize_slug_text("Hello 世界") == "hello"
        assert normalize_slug_text("Café") == "caf"

    def test_generate_random_suffix_length(self):
        """Test random suffix has correct length."""
        suffix = generate_random_suffix()
        assert len(suffix) == RANDOM_SUFFIX_LENGTH

    def test_generate_random_suffix_custom_length(self):
        """Test random suffix with custom length."""
        suffix = generate_random_suffix(8)
        assert len(suffix) == 8

    def test_generate_random_suffix_alphanumeric(self):
        """Test random suffix contains only lowercase alphanumeric."""
        suffix = generate_random_suffix()
        assert suffix.isalnum()
        assert suffix.islower()

    def test_generate_random_suffix_uniqueness(self):
        """Test random suffixes are different (high probability)."""
        suffixes = [generate_random_suffix() for _ in range(100)]
        # Should have high uniqueness (not all the same)
        assert len(set(suffixes)) > 90  # At least 90 unique out of 100


class TestSlugGeneration:
    """Test slug generation functions"""

    def test_generate_slug_basic(self):
        """Test basic slug generation from content."""
        slug = generate_slug("Hello World This Is My Note")
        assert slug == "hello-world-this-is-my"

    def test_generate_slug_special_characters(self):
        """Test slug generation removes special characters."""
        slug = generate_slug("Testing... with!@# special chars")
        assert slug == "testing-with-special-chars"

    def test_generate_slug_punctuation(self):
        """Test slug generation handles punctuation."""
        slug = generate_slug("What's up? How are you doing today?")
        assert slug == "whats-up-how-are-you"

    def test_generate_slug_numbers(self):
        """Test slug generation preserves numbers."""
        slug = generate_slug("The 2024 Guide to Python Programming")
        assert slug == "the-2024-guide-to-python"

    def test_generate_slug_first_five_words(self):
        """Test slug uses first 5 words (default)."""
        slug = generate_slug("One two three four five six seven eight")
        assert slug == "one-two-three-four-five"

    def test_generate_slug_fewer_than_five_words(self):
        """Test slug with fewer than 5 words."""
        slug = generate_slug("Three word note")
        assert slug == "three-word-note"

    def test_generate_slug_empty_content_raises_error(self):
        """Test slug generation raises error on empty content."""
        with pytest.raises(ValueError, match="Content cannot be empty"):
            generate_slug("")

    def test_generate_slug_whitespace_only_raises_error(self):
        """Test slug generation raises error on whitespace-only content."""
        with pytest.raises(ValueError, match="Content cannot be empty"):
            generate_slug("   \n\t   ")

    def test_generate_slug_special_chars_only_uses_timestamp(self):
        """Test slug falls back to timestamp when only special chars."""
        dt = datetime(2024, 11, 18, 14, 30, 45)
        slug = generate_slug("!@#$%^&*()", created_at=dt)
        assert slug == "20241118-143045"

    def test_generate_slug_very_short_content_uses_timestamp(self):
        """Test slug falls back to timestamp for very short content."""
        # Single character is valid after normalization
        slug = generate_slug("A")
        assert slug == "a"

        # But if normalized result is empty, uses timestamp
        dt = datetime(2024, 11, 18, 14, 30, 0)
        slug = generate_slug("!", created_at=dt)
        assert slug == "20241118-143000"

    def test_generate_slug_timestamp_fallback_uses_utcnow(self):
        """Test slug timestamp fallback uses current time if not provided."""
        slug = generate_slug("!@#$")
        # Should be in timestamp format
        assert len(slug) == 15  # YYYYMMDD-HHMMSS
        assert slug[8] == "-"
        assert slug[:8].isdigit()
        assert slug[9:].isdigit()

    def test_generate_slug_truncation(self):
        """Test slug is truncated to max length."""
        # Create very long content
        long_content = " ".join(["verylongword" for _ in range(20)])
        slug = generate_slug(long_content)
        assert len(slug) <= MAX_SLUG_LENGTH

    def test_generate_slug_unicode_content(self):
        """Test slug generation handles unicode gracefully."""
        slug = generate_slug("Hello 世界 World Python Programming")
        # Unicode is stripped, should keep ASCII words
        assert "hello" in slug
        assert "world" in slug

    def test_generate_slug_multiple_spaces(self):
        """Test slug handles multiple spaces between words."""
        slug = generate_slug("Hello    World    This    Is    Test")
        assert slug == "hello-world-this-is-test"


class TestSlugUniqueness:
    """Test slug uniqueness enforcement"""

    def test_make_slug_unique_no_collision(self):
        """Test make_slug_unique returns original if no collision."""
        slug = make_slug_unique("test-note", set())
        assert slug == "test-note"

    def test_make_slug_unique_with_collision(self):
        """Test make_slug_unique adds suffix on collision."""
        slug = make_slug_unique("test-note", {"test-note"})
        assert slug.startswith("test-note-")
        assert len(slug) == len("test-note-") + RANDOM_SUFFIX_LENGTH

    def test_make_slug_unique_suffix_is_alphanumeric(self):
        """Test unique slug suffix is alphanumeric."""
        slug = make_slug_unique("test-note", {"test-note"})
        suffix = slug.split("-")[-1]
        assert suffix.isalnum()
        assert suffix.islower()

    def test_make_slug_unique_multiple_collisions(self):
        """Test make_slug_unique handles multiple collisions."""
        existing = {"test-note", "test-note-abcd", "test-note-xyz1"}
        slug = make_slug_unique("test-note", existing)
        assert slug.startswith("test-note-")
        assert slug not in existing

    def test_make_slug_unique_generates_different_suffixes(self):
        """Test make_slug_unique generates different suffixes each time."""
        existing = {"test-note"}
        slugs = []
        for _ in range(10):
            slug = make_slug_unique("test-note", existing)
            slugs.append(slug)
            existing.add(slug)

        # All should be unique
        assert len(set(slugs)) == 10

    def test_make_slug_unique_empty_existing_set(self):
        """Test make_slug_unique with empty set."""
        slug = make_slug_unique("hello-world", set())
        assert slug == "hello-world"


class TestSlugValidation:
    """Test slug validation"""

    def test_validate_slug_valid_basic(self):
        """Test validation accepts valid basic slugs."""
        assert validate_slug("hello-world") is True
        assert validate_slug("test-note") is True
        assert validate_slug("my-first-post") is True

    def test_validate_slug_valid_with_numbers(self):
        """Test validation accepts slugs with numbers."""
        assert validate_slug("post-123") is True
        assert validate_slug("2024-11-18-note") is True
        assert validate_slug("test1-note2") is True

    def test_validate_slug_valid_single_character(self):
        """Test validation accepts single character slug."""
        assert validate_slug("a") is True
        assert validate_slug("1") is True

    def test_validate_slug_valid_max_length(self):
        """Test validation accepts slug at max length."""
        slug = "a" * MAX_SLUG_LENGTH
        assert validate_slug(slug) is True

    def test_validate_slug_invalid_empty(self):
        """Test validation rejects empty slug."""
        assert validate_slug("") is False

    def test_validate_slug_invalid_uppercase(self):
        """Test validation rejects uppercase characters."""
        assert validate_slug("Hello-World") is False
        assert validate_slug("TEST") is False

    def test_validate_slug_invalid_leading_hyphen(self):
        """Test validation rejects leading hyphen."""
        assert validate_slug("-hello") is False
        assert validate_slug("-test-note") is False

    def test_validate_slug_invalid_trailing_hyphen(self):
        """Test validation rejects trailing hyphen."""
        assert validate_slug("hello-") is False
        assert validate_slug("test-note-") is False

    def test_validate_slug_invalid_double_hyphen(self):
        """Test validation rejects consecutive hyphens."""
        assert validate_slug("hello--world") is False
        assert validate_slug("test---note") is False

    def test_validate_slug_invalid_underscore(self):
        """Test validation rejects underscores."""
        assert validate_slug("hello_world") is False
        assert validate_slug("test_note") is False

    def test_validate_slug_invalid_special_characters(self):
        """Test validation rejects special characters."""
        assert validate_slug("hello@world") is False
        assert validate_slug("test!note") is False
        assert validate_slug("note#123") is False
        assert validate_slug("hello.world") is False

    def test_validate_slug_invalid_spaces(self):
        """Test validation rejects spaces."""
        assert validate_slug("hello world") is False
        assert validate_slug("test note") is False

    def test_validate_slug_invalid_too_long(self):
        """Test validation rejects slug exceeding max length."""
        slug = "a" * (MAX_SLUG_LENGTH + 1)
        assert validate_slug(slug) is False

    def test_validate_slug_invalid_reserved_admin(self):
        """Test validation rejects reserved slug 'admin'."""
        assert validate_slug("admin") is False

    def test_validate_slug_invalid_reserved_api(self):
        """Test validation rejects reserved slug 'api'."""
        assert validate_slug("api") is False

    def test_validate_slug_invalid_reserved_static(self):
        """Test validation rejects reserved slug 'static'."""
        assert validate_slug("static") is False

    def test_validate_slug_invalid_reserved_auth(self):
        """Test validation rejects reserved slug 'auth'."""
        assert validate_slug("auth") is False

    def test_validate_slug_invalid_reserved_feed(self):
        """Test validation rejects reserved slug 'feed'."""
        assert validate_slug("feed") is False

    def test_validate_slug_invalid_reserved_login(self):
        """Test validation rejects reserved slug 'login'."""
        assert validate_slug("login") is False

    def test_validate_slug_invalid_reserved_logout(self):
        """Test validation rejects reserved slug 'logout'."""
        assert validate_slug("logout") is False


class TestEdgeCases:
    """Test edge cases and boundary conditions"""

    def test_slug_with_only_hyphens_after_normalization(self):
        """Test content that becomes only hyphens after normalization."""
        dt = datetime(2024, 11, 18, 15, 0, 0)
        slug = generate_slug("--- --- ---", created_at=dt)
        # Should fall back to timestamp
        assert slug == "20241118-150000"

    def test_slug_with_mixed_case(self):
        """Test slug generation normalizes mixed case."""
        slug = generate_slug("HeLLo WoRLd ThIs Is TeSt")
        assert slug == "hello-world-this-is-test"

    def test_slug_with_newlines(self):
        """Test slug generation handles newlines in content."""
        slug = generate_slug(
            "First line\nSecond line\nThird line\nFourth line\nFifth line"
        )
        assert slug == "first-line-second-line-third"

    def test_slug_with_tabs(self):
        """Test slug generation handles tabs."""
        slug = generate_slug("First\tSecond\tThird\tFourth\tFifth")
        assert slug == "first-second-third-fourth-fifth"

    def test_slug_timestamp_format(self):
        """Test timestamp format is correct."""
        dt = datetime(2024, 11, 18, 14, 30, 45)
        slug = generate_slug("@@@", created_at=dt)
        assert slug == "20241118-143045"
        # Verify format
        assert len(slug) == 15
        assert slug[8] == "-"

    def test_very_long_single_word(self):
        """Test slug with very long single word gets truncated."""
        long_word = "a" * 200
        slug = generate_slug(long_word)
        assert len(slug) <= MAX_SLUG_LENGTH
        assert slug == "a" * MAX_SLUG_LENGTH

    def test_slug_with_emoji(self):
        """Test slug generation strips emoji."""
        slug = generate_slug("Hello 🌍 World 🎉 This Is Test")
        # Emoji should be stripped, leaving only the first 5 words
        assert slug == "hello-world-this"

    def test_slug_uniqueness_with_reserved_base(self):
        """Test making reserved slug unique (though shouldn't happen)."""
        # If somehow a reserved slug needs uniqueness
        existing = {"admin"}
        slug = make_slug_unique("admin", existing)
        assert slug.startswith("admin-")
        assert validate_slug(slug) is True  # Should be valid after suffix

    def test_normalize_all_special_chars(self):
        """Test normalization with all special characters."""
        text = "!@#$%^&*()_+={}[]|\\:;\"'<>,.?/~`"
        result = normalize_slug_text(text)
        assert result == ""

    def test_generate_slug_with_markdown_heading(self):
        """Test slug generation from markdown heading."""
        slug = generate_slug("# My First Blog Post About Python")
        # The # should be treated as special char and removed, first 5 words
        assert slug == "my-first-blog-post"


class TestSecurityCases:
    """Test security-related edge cases"""

    def test_slug_no_path_traversal_characters(self):
        """Test slug doesn't contain path traversal patterns."""
        slug = generate_slug("../../etc/passwd is the test note")
        assert ".." not in slug
        assert "/" not in slug
        # Dots/slashes removed, "../../etc/passwd" becomes "etcpasswd"
        # Then "is the test note" = 4 more words, total 5 words
        assert slug == "etcpasswd-is-the-test-note"

    def test_slug_no_null_bytes(self):
        """Test slug handles null bytes safely."""
        # Python strings can contain null bytes
        content = "Test\x00note\x00content"
        slug = generate_slug(content)
        assert "\x00" not in slug

    def test_slug_no_sql_injection_patterns(self):
        """Test slug doesn't preserve SQL injection patterns."""
        slug = generate_slug("'; DROP TABLE notes; -- is my note")
        # Should be normalized, no special SQL chars
        assert ";" not in slug
        assert "'" not in slug
        assert "--" not in slug

    def test_slug_no_script_tags(self):
        """Test slug doesn't preserve script tags."""
        slug = generate_slug("<script>alert('xss')</script> My Note Title")
        assert "<" not in slug
        assert ">" not in slug
        assert "script" in slug  # The word itself is fine
        # Special chars removed, becomes one word, then first 5 words total
        assert slug == "scriptalertxssscript-my-note-title"

    def test_random_suffix_uses_secrets_module(self):
        """Test random suffix is cryptographically secure (not predictable)."""
        # Generate many suffixes and ensure high entropy
        suffixes = [generate_random_suffix() for _ in range(1000)]
        unique_count = len(set(suffixes))
        # Should have very high uniqueness (>99%)
        assert unique_count > 990


class TestContentHashing:
    """Test content hashing functions"""

    def test_calculate_content_hash_consistency(self):
        """Test hash is consistent for same content."""
        hash1 = calculate_content_hash("Test content")
        hash2 = calculate_content_hash("Test content")
        assert hash1 == hash2

    def test_calculate_content_hash_different(self):
        """Test different content produces different hash."""
        hash1 = calculate_content_hash("Test content 1")
        hash2 = calculate_content_hash("Test content 2")
        assert hash1 != hash2

    def test_calculate_content_hash_empty(self):
        """Test hash of empty string."""
        hash_empty = calculate_content_hash("")
        assert len(hash_empty) == 64  # SHA-256 produces 64 hex chars
        assert hash_empty.isalnum()

    def test_calculate_content_hash_unicode(self):
        """Test hash handles unicode correctly."""
        hash_val = calculate_content_hash("Hello 世界")
        assert len(hash_val) == 64
        assert hash_val.isalnum()

    def test_calculate_content_hash_known_value(self):
        """Test hash matches known SHA-256 value."""
        # Known SHA-256 hash for "Hello World"
        expected = "a591a6d40bf420404a011733cfb7b190d62c65bf0bcda32b57b277d9ad9f146e"
        actual = calculate_content_hash("Hello World")
        assert actual == expected

    def test_calculate_content_hash_multiline(self):
        """Test hash of multiline content."""
        content = "Line 1\nLine 2\nLine 3"
        hash_val = calculate_content_hash(content)
        assert len(hash_val) == 64

    def test_calculate_content_hash_special_characters(self):
        """Test hash handles special characters."""
        content = "Special chars: !@#$%^&*()_+-=[]{}|;:',.<>?/~`"
        hash_val = calculate_content_hash(content)
        assert len(hash_val) == 64


class TestFilePathOperations:
    """Test file path generation and validation"""

    def test_generate_note_path_basic(self):
        """Test basic note path generation."""
        dt = datetime(2024, 11, 18, 14, 30)
        path = generate_note_path("test-note", dt, Path("data"))
        assert path == Path("data/notes/2024/11/test-note.md")

    def test_generate_note_path_different_months(self):
        """Test path generation for different months."""
        dt_jan = datetime(2024, 1, 5, 10, 0)
        dt_dec = datetime(2024, 12, 25, 15, 30)

        path_jan = generate_note_path("jan-note", dt_jan, Path("data"))
        path_dec = generate_note_path("dec-note", dt_dec, Path("data"))

        assert path_jan == Path("data/notes/2024/01/jan-note.md")
        assert path_dec == Path("data/notes/2024/12/dec-note.md")

    def test_generate_note_path_different_years(self):
        """Test path generation for different years."""
        dt_2024 = datetime(2024, 6, 15)
        dt_2025 = datetime(2025, 6, 15)

        path_2024 = generate_note_path("note-2024", dt_2024, Path("data"))
        path_2025 = generate_note_path("note-2025", dt_2025, Path("data"))

        assert path_2024 == Path("data/notes/2024/06/note-2024.md")
        assert path_2025 == Path("data/notes/2025/06/note-2025.md")

    def test_generate_note_path_invalid_slug(self):
        """Test note path generation rejects invalid slug."""
        dt = datetime(2024, 11, 18)
        with pytest.raises(ValueError, match="Invalid slug"):
            generate_note_path("Invalid Slug!", dt, Path("data"))

    def test_generate_note_path_with_numbers(self):
        """Test path generation with slug containing numbers."""
        dt = datetime(2024, 11, 18)
        path = generate_note_path("note-123-test", dt, Path("data"))
        assert path == Path("data/notes/2024/11/note-123-test.md")

    def test_ensure_note_directory_creates_dirs(self, tmp_path):
        """Test ensure_note_directory creates directories."""
        note_path = tmp_path / "notes" / "2024" / "11" / "test.md"
        assert not note_path.parent.exists()

        result = ensure_note_directory(note_path)

        assert note_path.parent.exists()
        assert result == note_path.parent

    def test_ensure_note_directory_existing_dirs(self, tmp_path):
        """Test ensure_note_directory with existing directories."""
        note_path = tmp_path / "notes" / "2024" / "11" / "test.md"
        note_path.parent.mkdir(parents=True)

        # Should not raise error
        result = ensure_note_directory(note_path)
        assert result == note_path.parent

    def test_ensure_note_directory_deep_structure(self, tmp_path):
        """Test ensure_note_directory with deep directory structure."""
        note_path = tmp_path / "a" / "b" / "c" / "d" / "e" / "test.md"
        result = ensure_note_directory(note_path)

        assert note_path.parent.exists()
        assert result == note_path.parent

    def test_validate_note_path_safe(self, tmp_path):
        """Test path validation accepts safe paths."""
        note_path = tmp_path / "notes" / "2024" / "11" / "note.md"
        assert validate_note_path(note_path, tmp_path) is True

    def test_validate_note_path_traversal_dotdot(self, tmp_path):
        """Test path validation rejects .. traversal."""
        note_path = tmp_path / "notes" / ".." / ".." / "etc" / "passwd"
        assert validate_note_path(note_path, tmp_path) is False

    def test_validate_note_path_absolute_outside(self, tmp_path):
        """Test path validation rejects absolute paths outside data dir."""
        assert validate_note_path(Path("/etc/passwd"), tmp_path) is False

    def test_validate_note_path_within_subdirectory(self, tmp_path):
        """Test path validation accepts paths in subdirectories."""
        note_path = tmp_path / "notes" / "2024" / "11" / "subfolder" / "note.md"
        assert validate_note_path(note_path, tmp_path) is True

    def test_validate_note_path_symlink_outside(self, tmp_path):
        """Test path validation handles symlinks pointing outside."""
        # Create a symlink pointing outside data_dir
        outside_dir = tmp_path.parent / "outside"
        outside_dir.mkdir(exist_ok=True)

        link_path = tmp_path / "link"
        link_path.symlink_to(outside_dir)

        target_path = link_path / "file.md"
        assert validate_note_path(target_path, tmp_path) is False

    def test_validate_note_path_same_directory(self, tmp_path):
        """Test path validation for file in data_dir root."""
        note_path = tmp_path / "note.md"
        assert validate_note_path(note_path, tmp_path) is True


class TestAtomicFileOperations:
    """Test atomic file write/read/delete operations"""

    def test_write_and_read_note_file(self, tmp_path):
        """Test writing and reading note file."""
        file_path = tmp_path / "test.md"
        content = "# Test Note\n\nThis is a test."

        write_note_file(file_path, content)
        assert file_path.exists()

        read_content = read_note_file(file_path)
        assert read_content == content

    def test_write_note_file_atomic(self, tmp_path):
        """Test write is atomic (temp file cleaned up)."""
        file_path = tmp_path / "test.md"
        temp_path = file_path.with_suffix(".md.tmp")

        write_note_file(file_path, "Test")

        # Temp file should not exist after write
        assert not temp_path.exists()
        assert file_path.exists()

    def test_write_note_file_overwrites(self, tmp_path):
        """Test writing overwrites existing file."""
        file_path = tmp_path / "test.md"

        write_note_file(file_path, "Original content")
        write_note_file(file_path, "New content")

        content = read_note_file(file_path)
        assert content == "New content"

    def test_write_note_file_unicode(self, tmp_path):
        """Test writing unicode content."""
        file_path = tmp_path / "test.md"
        content = "Unicode: 你好世界 🌍"

        write_note_file(file_path, content)
        read_content = read_note_file(file_path)

        assert read_content == content

    def test_write_note_file_empty(self, tmp_path):
        """Test writing empty file."""
        file_path = tmp_path / "test.md"
        write_note_file(file_path, "")

        content = read_note_file(file_path)
        assert content == ""

    def test_write_note_file_multiline(self, tmp_path):
        """Test writing multiline content."""
        file_path = tmp_path / "test.md"
        content = "Line 1\nLine 2\nLine 3\n"

        write_note_file(file_path, content)
        read_content = read_note_file(file_path)

        assert read_content == content

    def test_read_note_file_not_found(self, tmp_path):
        """Test reading non-existent file raises error."""
        file_path = tmp_path / "nonexistent.md"
        with pytest.raises(FileNotFoundError):
            read_note_file(file_path)

    def test_delete_note_file_hard(self, tmp_path):
        """Test hard delete removes file."""
        file_path = tmp_path / "test.md"
        file_path.write_text("Test")

        delete_note_file(file_path, soft=False)
        assert not file_path.exists()

    def test_delete_note_file_soft(self, tmp_path):
        """Test soft delete moves file to trash."""
        # Create note file
        notes_dir = tmp_path / "notes" / "2024" / "11"
        notes_dir.mkdir(parents=True)
        file_path = notes_dir / "test.md"
        file_path.write_text("Test")

        # Soft delete
        delete_note_file(file_path, soft=True, data_dir=tmp_path)

        # Original should be gone
        assert not file_path.exists()

        # Should be in trash
        trash_path = tmp_path / TRASH_DIR_NAME / "2024" / "11" / "test.md"
        assert trash_path.exists()
        assert trash_path.read_text() == "Test"

    def test_delete_note_file_soft_without_data_dir(self, tmp_path):
        """Test soft delete requires data_dir."""
        file_path = tmp_path / "test.md"
        file_path.write_text("Test")

        with pytest.raises(ValueError, match="data_dir is required"):
            delete_note_file(file_path, soft=True, data_dir=None)

    def test_delete_note_file_soft_different_months(self, tmp_path):
        """Test soft delete preserves year/month structure."""
        # Create note in January
        jan_dir = tmp_path / "notes" / "2024" / "01"
        jan_dir.mkdir(parents=True)
        jan_file = jan_dir / "jan-note.md"
        jan_file.write_text("January note")

        # Create note in December
        dec_dir = tmp_path / "notes" / "2024" / "12"
        dec_dir.mkdir(parents=True)
        dec_file = dec_dir / "dec-note.md"
        dec_file.write_text("December note")

        # Soft delete both
        delete_note_file(jan_file, soft=True, data_dir=tmp_path)
        delete_note_file(dec_file, soft=True, data_dir=tmp_path)

        # Check trash structure
        jan_trash = tmp_path / TRASH_DIR_NAME / "2024" / "01" / "jan-note.md"
        dec_trash = tmp_path / TRASH_DIR_NAME / "2024" / "12" / "dec-note.md"

        assert jan_trash.exists()
        assert dec_trash.exists()

    def test_delete_note_file_hard_not_found(self, tmp_path):
        """Test hard delete of non-existent file raises error."""
        file_path = tmp_path / "nonexistent.md"
        with pytest.raises(FileNotFoundError):
            delete_note_file(file_path, soft=False)


class TestDateTimeFormatting:
    """Test date/time formatting functions"""

    def test_format_rfc822_basic(self):
        """Test RFC-822 date formatting."""
        dt = datetime(2024, 11, 18, 14, 30, 45)
        formatted = format_rfc822(dt)
        assert formatted == "Mon, 18 Nov 2024 14:30:45 +0000"

    def test_format_rfc822_different_dates(self):
        """Test RFC-822 formatting for different dates."""
        dt1 = datetime(2024, 1, 1, 0, 0, 0)
        dt2 = datetime(2024, 12, 31, 23, 59, 59)

        assert format_rfc822(dt1) == "Mon, 01 Jan 2024 00:00:00 +0000"
        assert format_rfc822(dt2) == "Tue, 31 Dec 2024 23:59:59 +0000"

    def test_format_rfc822_weekdays(self):
        """Test RFC-822 format includes correct weekday."""
        # Known dates and weekdays
        monday = datetime(2024, 11, 18, 12, 0, 0)
        friday = datetime(2024, 11, 22, 12, 0, 0)
        sunday = datetime(2024, 11, 24, 12, 0, 0)

        assert format_rfc822(monday).startswith("Mon,")
        assert format_rfc822(friday).startswith("Fri,")
        assert format_rfc822(sunday).startswith("Sun,")

    def test_format_iso8601_basic(self):
        """Test ISO 8601 date formatting."""
        dt = datetime(2024, 11, 18, 14, 30, 45)
        formatted = format_iso8601(dt)
        assert formatted == "2024-11-18T14:30:45Z"

    def test_format_iso8601_different_dates(self):
        """Test ISO 8601 formatting for different dates."""
        dt1 = datetime(2024, 1, 1, 0, 0, 0)
        dt2 = datetime(2024, 12, 31, 23, 59, 59)

        assert format_iso8601(dt1) == "2024-01-01T00:00:00Z"
        assert format_iso8601(dt2) == "2024-12-31T23:59:59Z"

    def test_format_iso8601_single_digits(self):
        """Test ISO 8601 format pads single digits."""
        dt = datetime(2024, 1, 5, 9, 8, 7)
        formatted = format_iso8601(dt)
        assert formatted == "2024-01-05T09:08:07Z"

    def test_parse_iso8601_basic(self):
        """Test ISO 8601 date parsing."""
        dt = parse_iso8601("2024-11-18T14:30:45Z")
        assert dt.year == 2024
        assert dt.month == 11
        assert dt.day == 18
        assert dt.hour == 14
        assert dt.minute == 30
        assert dt.second == 45

    def test_parse_iso8601_without_z(self):
        """Test ISO 8601 parsing without Z suffix."""
        dt = parse_iso8601("2024-11-18T14:30:45")
        assert dt.year == 2024
        assert dt.month == 11
        assert dt.day == 18

    def test_parse_iso8601_roundtrip(self):
        """Test ISO 8601 format and parse roundtrip."""
        original = datetime(2024, 11, 18, 14, 30, 45)
        formatted = format_iso8601(original)
        parsed = parse_iso8601(formatted)

        assert parsed == original

    def test_parse_iso8601_invalid_format(self):
        """Test ISO 8601 parsing rejects invalid format."""
        with pytest.raises(ValueError):
            parse_iso8601("not-a-date")

    def test_parse_iso8601_invalid_date(self):
        """Test ISO 8601 parsing rejects invalid date values."""
        with pytest.raises(ValueError):
            parse_iso8601("2024-13-01T00:00:00Z")  # Invalid month

    def test_format_and_parse_consistency(self):
        """Test RFC-822 and ISO 8601 are both consistent."""
        dt = datetime(2024, 11, 18, 14, 30, 45)

        # ISO 8601 roundtrip
        iso_formatted = format_iso8601(dt)
        iso_parsed = parse_iso8601(iso_formatted)
        assert iso_parsed == dt

        # RFC-822 format is consistent
        rfc_formatted = format_rfc822(dt)
        assert "2024" in rfc_formatted
        assert "14:30:45" in rfc_formatted
