import pytest
from unittest.mock import MagicMock
from bson import ObjectId
from app.repositories.subject_repository import SubjectRepository
from app.models.entities import SubjectEntity


@pytest.fixture
def mock_db():
    return MagicMock()


@pytest.fixture
def repository(mock_db):
    return SubjectRepository(db=mock_db)


def generate_subject(name, short_name, desc, icon="") -> SubjectEntity:
    return SubjectEntity(name=name, short_name=short_name, description=desc, icon=icon)


def test_create_subject(repository):
    subject = generate_subject(
        name="Sejarah Indonesia",
        short_name="SEJARAH",
        desc="Mempelajari peristiwa dan tokoh penting dalam sejarah nasional.",
    )
    insert_result = MagicMock(inserted_id=ObjectId())
    repository.collection.insert_one.return_value = insert_result

    result = repository.create(subject)

    repository.collection.insert_one.assert_called_once()
    assert isinstance(result, str)


def test_get_all_subjects(repository):
    mock_data = [
        generate_subject(
            "Biologi",
            "BIO",
            "Ilmu yang mempelajari makhluk hidup dan lingkungannya.",
        ).model_dump(by_alias=True),
        generate_subject(
            "Fisika",
            "FIS",
            "Mempelajari fenomena alam seperti gaya, energi, dan gerak.",
        ).model_dump(by_alias=True),
    ]
    repository.collection.find.return_value = mock_data

    result = repository.get_all()

    assert len(result) == 2
    assert result[0].name == "Biologi"
    assert result[1].short_name == "FIS"


def test_get_by_id_valid(repository):
    oid = ObjectId()
    doc = generate_subject(
        "Geografi",
        "GEO",
        "Studi tentang lokasi, wilayah, dan interaksi manusia-lingkungan.",
    ).model_dump(by_alias=True)
    doc["_id"] = oid
    repository.collection.find_one.return_value = doc

    result = repository.get_by_id(str(oid))

    assert isinstance(result, SubjectEntity)
    assert result.name == "Geografi"
    assert result.id == oid


def test_get_by_id_invalid(repository):
    result = repository.get_by_id("invalid_id")
    assert result is None


def test_get_by_ids_mixed(repository):
    oid1 = ObjectId()
    oid2 = ObjectId()
    ids = [str(oid1), "invalid_id", str(oid2)]

    mock_docs = [
        {
            **generate_subject(
                "Kimia",
                "KIM",
                "Ilmu tentang zat dan reaksinya",
            ).model_dump(by_alias=True),
            "_id": oid1,
        },
        {
            **generate_subject(
                "Ekonomi",
                "EKO",
                "Analisis produksi, distribusi, dan konsumsi",
            ).model_dump(by_alias=True),
            "_id": oid2,
        },
    ]
    repository.collection.find.return_value = mock_docs

    result = repository.get_by_ids(ids)

    assert len(result) == 2
    assert result[0].short_name == "KIM"
    assert result[1].name == "Ekonomi"


def test_update_valid(repository):
    oid = ObjectId()
    repository.collection.update_one.return_value = MagicMock(modified_count=1)

    result = repository.update(str(oid), {"description": "Updated Description"})

    assert result is True


def test_update_invalid(repository):
    result = repository.update("invalid_object_id", {"name": "Something"})

    assert result is False


def test_delete_valid(repository):
    oid = ObjectId()
    repository.collection.delete_one.return_value = MagicMock(deleted_count=1)

    result = repository.delete(str(oid))

    assert result is True


def test_delete_invalid(repository):
    result = repository.delete("invalid_object_id")
    assert result is False
