"""
Test suite for ClientNote model.
Tests model creation, relationships, properties, and business logic.
"""

import pytest
from datetime import datetime
from app.models import ClientNote, Client, User
from app import db


# ============================================================================
# ClientNote Model Tests
# ============================================================================


@pytest.mark.unit
@pytest.mark.models
@pytest.mark.smoke
def test_client_note_creation(app, user, test_client):
    """Test basic client note creation."""
    with app.app_context():
        note = ClientNote(
            content="Important note about the client", user_id=user.id, client_id=test_client.id, is_important=False
        )
        db.session.add(note)
        db.session.commit()

        assert note.id is not None
        assert note.content == "Important note about the client"
        assert note.user_id == user.id
        assert note.client_id == test_client.id
        assert note.is_important is False
        assert note.created_at is not None
        assert note.updated_at is not None


@pytest.mark.unit
@pytest.mark.models
def test_client_note_requires_client(app, user):
    """Test that client note requires a client."""
    with app.app_context():
        with pytest.raises(ValueError, match="Note must be associated with a client"):
            note = ClientNote(content="Note without client", user_id=user.id, client_id=None)


@pytest.mark.unit
@pytest.mark.models
def test_client_note_requires_content(app, user, test_client):
    """Test that client note requires content."""
    with app.app_context():
        with pytest.raises(ValueError, match="Note content cannot be empty"):
            note = ClientNote(content="", user_id=user.id, client_id=test_client.id)


@pytest.mark.unit
@pytest.mark.models
def test_client_note_strips_content(app, user, test_client):
    """Test that client note content is stripped of whitespace."""
    with app.app_context():
        note = ClientNote(content="  Note with spaces  ", user_id=user.id, client_id=test_client.id)
        db.session.add(note)
        db.session.commit()

        assert note.content == "Note with spaces"


@pytest.mark.unit
@pytest.mark.models
def test_client_note_author_relationship(app, user, test_client):
    """Test client note author relationship."""
    with app.app_context():
        note = ClientNote(content="Test note", user_id=user.id, client_id=test_client.id)
        db.session.add(note)
        db.session.commit()

        db.session.refresh(note)
        assert note.author is not None
        assert note.author.id == user.id
        assert note.author.username == user.username


@pytest.mark.unit
@pytest.mark.models
def test_client_note_client_relationship(app, user, test_client):
    """Test client note client relationship."""
    with app.app_context():
        note = ClientNote(content="Test note", user_id=user.id, client_id=test_client.id)
        db.session.add(note)
        db.session.commit()

        db.session.refresh(note)
        assert note.client is not None
        assert note.client.id == test_client.id
        assert note.client.name == test_client.name


@pytest.mark.unit
@pytest.mark.models
def test_client_has_notes_relationship(app, user, test_client):
    """Test that client has notes relationship."""
    with app.app_context():
        # Re-query the client to ensure it's in the current session
        from app.models import Client

        client = Client.query.get(test_client.id)

        note1 = ClientNote(content="First note", user_id=user.id, client_id=client.id)
        note2 = ClientNote(content="Second note", user_id=user.id, client_id=client.id, is_important=True)
        db.session.add_all([note1, note2])
        db.session.commit()

        db.session.refresh(client)
        assert client.notes.count() == 2


@pytest.mark.unit
@pytest.mark.models
def test_client_note_author_name_property(app, test_client):
    """Test client note author_name property."""
    with app.app_context():
        # Test with username only (no full_name)
        user_without_fullname = User(username="usernoname", email="noname@example.com", role="user")
        user_without_fullname.is_active = True
        db.session.add(user_without_fullname)
        db.session.commit()

        note1 = ClientNote(content="Test note 1", user_id=user_without_fullname.id, client_id=test_client.id)
        db.session.add(note1)
        db.session.commit()

        db.session.refresh(note1)
        assert note1.author_name == "usernoname"

        # Test with full name
        user_with_fullname = User(username="userwithname", email="withname@example.com", role="user")
        user_with_fullname.full_name = "Test User Full Name"
        user_with_fullname.is_active = True
        db.session.add(user_with_fullname)
        db.session.commit()

        note2 = ClientNote(content="Test note 2", user_id=user_with_fullname.id, client_id=test_client.id)
        db.session.add(note2)
        db.session.commit()

        db.session.refresh(note2)
        assert note2.author_name == "Test User Full Name"


@pytest.mark.unit
@pytest.mark.models
def test_client_note_client_name_property(app, user, test_client):
    """Test client note client_name property."""
    with app.app_context():
        note = ClientNote(content="Test note", user_id=user.id, client_id=test_client.id)
        db.session.add(note)
        db.session.commit()

        db.session.refresh(note)
        assert note.client_name == test_client.name


@pytest.mark.unit
@pytest.mark.models
def test_client_note_can_edit(app, user, admin_user, test_client):
    """Test client note can_edit permission."""
    with app.app_context():
        note = ClientNote(content="Test note", user_id=user.id, client_id=test_client.id)
        db.session.add(note)
        db.session.commit()

        # Author can edit
        assert note.can_edit(user) is True

        # Admin can edit
        assert note.can_edit(admin_user) is True

        # Other user cannot edit
        other_user = User(username="otheruser", role="user")
        other_user.is_active = True
        db.session.add(other_user)
        db.session.commit()

        assert note.can_edit(other_user) is False


@pytest.mark.unit
@pytest.mark.models
def test_client_note_can_delete(app, user, admin_user, test_client):
    """Test client note can_delete permission."""
    with app.app_context():
        note = ClientNote(content="Test note", user_id=user.id, client_id=test_client.id)
        db.session.add(note)
        db.session.commit()

        # Author can delete
        assert note.can_delete(user) is True

        # Admin can delete
        assert note.can_delete(admin_user) is True

        # Other user cannot delete
        other_user = User(username="otheruser", role="user")
        other_user.is_active = True
        db.session.add(other_user)
        db.session.commit()

        assert note.can_delete(other_user) is False


@pytest.mark.unit
@pytest.mark.models
def test_client_note_edit_content(app, user, test_client):
    """Test editing client note content."""
    with app.app_context():
        note = ClientNote(content="Original content", user_id=user.id, client_id=test_client.id, is_important=False)
        db.session.add(note)
        db.session.commit()

        # Edit content
        note.edit_content("Updated content", user, is_important=True)
        db.session.commit()

        assert note.content == "Updated content"
        assert note.is_important is True


@pytest.mark.unit
@pytest.mark.models
def test_client_note_edit_content_permission_denied(app, user, test_client):
    """Test editing client note without permission."""
    with app.app_context():
        note = ClientNote(content="Original content", user_id=user.id, client_id=test_client.id)
        db.session.add(note)
        db.session.commit()

        # Create another user
        other_user = User(username="otheruser", role="user")
        other_user.is_active = True
        db.session.add(other_user)
        db.session.commit()

        # Try to edit as other user
        with pytest.raises(PermissionError, match="User does not have permission to edit this note"):
            note.edit_content("Hacked content", other_user)


@pytest.mark.unit
@pytest.mark.models
def test_client_note_edit_content_empty_fails(app, user, test_client):
    """Test editing client note with empty content fails."""
    with app.app_context():
        note = ClientNote(content="Original content", user_id=user.id, client_id=test_client.id)
        db.session.add(note)
        db.session.commit()

        # Try to edit with empty content
        with pytest.raises(ValueError, match="Note content cannot be empty"):
            note.edit_content("", user)


@pytest.mark.unit
@pytest.mark.models
def test_client_note_to_dict(app, user, test_client):
    """Test client note serialization to dictionary."""
    with app.app_context():
        note = ClientNote(content="Test note", user_id=user.id, client_id=test_client.id, is_important=True)
        db.session.add(note)
        db.session.commit()

        db.session.refresh(note)
        note_dict = note.to_dict()

        assert "id" in note_dict
        assert "content" in note_dict
        assert "client_id" in note_dict
        assert "client_name" in note_dict
        assert "user_id" in note_dict
        assert "author" in note_dict
        assert "author_name" in note_dict
        assert "is_important" in note_dict
        assert "created_at" in note_dict
        assert "updated_at" in note_dict

        assert note_dict["content"] == "Test note"
        assert note_dict["is_important"] is True


@pytest.mark.unit
@pytest.mark.models
def test_get_client_notes(app, user, test_client):
    """Test getting notes for a client."""
    with app.app_context():
        # Create multiple notes
        note1 = ClientNote(content="First note", user_id=user.id, client_id=test_client.id, is_important=False)
        note2 = ClientNote(content="Second note", user_id=user.id, client_id=test_client.id, is_important=True)
        note3 = ClientNote(content="Third note", user_id=user.id, client_id=test_client.id, is_important=False)
        db.session.add_all([note1, note2, note3])
        db.session.commit()

        # Get all notes
        notes = ClientNote.get_client_notes(test_client.id)
        assert len(notes) == 3

        # Get notes ordered by importance
        notes_ordered = ClientNote.get_client_notes(test_client.id, order_by_important=True)
        assert len(notes_ordered) == 3
        # Important note should be first
        assert notes_ordered[0].is_important is True


@pytest.mark.unit
@pytest.mark.models
def test_get_important_notes(app, user, test_client):
    """Test getting only important notes."""
    with app.app_context():
        # Create multiple notes
        note1 = ClientNote(content="Regular note", user_id=user.id, client_id=test_client.id, is_important=False)
        note2 = ClientNote(content="Important note 1", user_id=user.id, client_id=test_client.id, is_important=True)
        note3 = ClientNote(content="Important note 2", user_id=user.id, client_id=test_client.id, is_important=True)
        db.session.add_all([note1, note2, note3])
        db.session.commit()

        # Get all important notes
        important_notes = ClientNote.get_important_notes()
        assert len(important_notes) == 2
        assert all(note.is_important for note in important_notes)

        # Get important notes for specific client
        client_important = ClientNote.get_important_notes(client_id=test_client.id)
        assert len(client_important) == 2


@pytest.mark.unit
@pytest.mark.models
def test_get_user_notes(app, user, test_client):
    """Test getting notes by a specific user."""
    with app.app_context():
        # Create notes by user
        note1 = ClientNote(content="User note 1", user_id=user.id, client_id=test_client.id)
        note2 = ClientNote(content="User note 2", user_id=user.id, client_id=test_client.id)
        db.session.add_all([note1, note2])

        # Create note by other user
        other_user = User(username="otheruser", role="user")
        other_user.is_active = True
        db.session.add(other_user)
        db.session.commit()

        note3 = ClientNote(content="Other user note", user_id=other_user.id, client_id=test_client.id)
        db.session.add(note3)
        db.session.commit()

        # Get notes by specific user
        user_notes = ClientNote.get_user_notes(user.id)
        assert len(user_notes) == 2
        assert all(note.user_id == user.id for note in user_notes)

        # Test with limit
        limited_notes = ClientNote.get_user_notes(user.id, limit=1)
        assert len(limited_notes) == 1


@pytest.mark.unit
@pytest.mark.models
def test_get_recent_notes(app, user, test_client):
    """Test getting recent notes across all clients."""
    with app.app_context():
        # Create multiple notes
        for i in range(15):
            note = ClientNote(content=f"Note {i}", user_id=user.id, client_id=test_client.id)
            db.session.add(note)
        db.session.commit()

        # Get recent notes with default limit
        recent_notes = ClientNote.get_recent_notes()
        assert len(recent_notes) == 10

        # Get recent notes with custom limit
        recent_notes_5 = ClientNote.get_recent_notes(limit=5)
        assert len(recent_notes_5) == 5


@pytest.mark.unit
@pytest.mark.models
def test_client_note_repr(app, user, test_client):
    """Test client note string representation."""
    with app.app_context():
        note = ClientNote(content="Test note", user_id=user.id, client_id=test_client.id)
        db.session.add(note)
        db.session.commit()

        db.session.refresh(note)
        repr_str = repr(note)
        assert "ClientNote" in repr_str
        assert user.username in repr_str
        assert str(test_client.id) in repr_str


@pytest.mark.unit
@pytest.mark.models
def test_client_note_cascade_delete(app, user, test_client):
    """Test that notes are deleted when client is deleted."""
    with app.app_context():
        # Re-query the client to ensure it's in the current session
        from app.models import Client

        client = Client.query.get(test_client.id)

        note = ClientNote(content="Test note", user_id=user.id, client_id=client.id)
        db.session.add(note)
        db.session.commit()

        note_id = note.id

        # Delete client
        db.session.delete(client)
        db.session.commit()

        # Note should be deleted
        deleted_note = ClientNote.query.get(note_id)
        assert deleted_note is None
