# ruff: noqa: E501

import unittest
from unittest.mock import patch

from chromadb.config import Settings

from embedchain import App
from embedchain.config import AppConfig, ChromaDbConfig
from embedchain.vectordb.chroma import ChromaDB


class TestChromaDbHosts(unittest.TestCase):
    def test_init_with_host_and_port(self):
        """
        Test if the `ChromaDB` instance is initialized with the correct host and port values.
        """
        host = "test-host"
        port = "1234"
        config = ChromaDbConfig(host=host, port=port)

        db = ChromaDB(config=config)
        settings = db.client.get_settings()
        self.assertEqual(settings.chroma_server_host, host)
        self.assertEqual(settings.chroma_server_http_port, port)

    def test_init_with_basic_auth(self):
        host = "test-host"
        port = "1234"

        chroma_auth_settings = {
            "chroma_client_auth_provider": "chromadb.auth.basic.BasicAuthClientProvider",
            "chroma_client_auth_credentials": "admin:admin",
        }

        config = ChromaDbConfig(host=host, port=port, chroma_settings=chroma_auth_settings)
        db = ChromaDB(config=config)
        settings = db.client.get_settings()
        self.assertEqual(settings.chroma_server_host, host)
        self.assertEqual(settings.chroma_server_http_port, port)
        self.assertEqual(settings.chroma_client_auth_provider, chroma_auth_settings["chroma_client_auth_provider"])
        self.assertEqual(
            settings.chroma_client_auth_credentials, chroma_auth_settings["chroma_client_auth_credentials"]
        )


# Review this test
class TestChromaDbHostsInit(unittest.TestCase):
    @patch("embedchain.vectordb.chroma.chromadb.Client")
    def test_app_init_with_host_and_port(self, mock_client):
        """
        Test if the `App` instance is initialized with the correct host and port values.
        """
        host = "test-host"
        port = "1234"

        config = AppConfig(collect_metrics=False)
        chromadb_config = ChromaDbConfig(host=host, port=port)

        _app = App(config, chromadb_config=chromadb_config)

        called_settings: Settings = mock_client.call_args[0][0]

        self.assertEqual(called_settings.chroma_server_host, host)
        self.assertEqual(called_settings.chroma_server_http_port, port)


class TestChromaDbHostsNone(unittest.TestCase):
    @patch("embedchain.vectordb.chroma.chromadb.Client")
    def test_init_with_host_and_port_none(self, mock_client):
        """
        Test if the `App` instance is initialized without default hosts and ports.
        """

        _app = App(config=AppConfig(collect_metrics=False))

        called_settings: Settings = mock_client.call_args[0][0]
        self.assertEqual(called_settings.chroma_server_host, None)
        self.assertEqual(called_settings.chroma_server_http_port, None)


class TestChromaDbHostsLoglevel(unittest.TestCase):
    @patch("embedchain.vectordb.chroma.chromadb.Client")
    def test_init_with_host_and_port_log_level(self, mock_client):
        """
        Test if the `App` instance is initialized without a config that does not contain default hosts and ports.
        """

        _app = App(config=AppConfig(collect_metrics=False))

        self.assertEqual(mock_client.call_args[0][0].chroma_server_host, None)
        self.assertEqual(mock_client.call_args[0][0].chroma_server_http_port, None)


class TestChromaDbDuplicateHandling:
    chroma_settings = {"allow_reset": True}
    chroma_config = ChromaDbConfig(chroma_settings=chroma_settings)
    app_config = AppConfig(collection_name=False, collect_metrics=False)
    app_with_settings = App(config=app_config, chromadb_config=chroma_config)

    def test_duplicates_throw_warning(self, caplog):
        """
        Test that add duplicates throws an error.
        """
        # Start with a clean app
        self.app_with_settings.reset()

        app = App(config=AppConfig(collect_metrics=False))
        app.db.collection.add(embeddings=[[0, 0, 0]], ids=["0"])
        app.db.collection.add(embeddings=[[0, 0, 0]], ids=["0"])
        assert "Insert of existing embedding ID: 0" in caplog.text
        assert "Add of existing embedding ID: 0" in caplog.text

    def test_duplicates_collections_no_warning(self, caplog):
        """
        Test that different collections can have duplicates.
        """
        # NOTE: Not part of the TestChromaDbCollection because `unittest.TestCase` doesn't have caplog.

        # Start with a clean app
        self.app_with_settings.reset()

        app = App(config=AppConfig(collect_metrics=False))
        app.set_collection_name("test_collection_1")
        app.db.collection.add(embeddings=[[0, 0, 0]], ids=["0"])
        app.set_collection_name("test_collection_2")
        app.db.collection.add(embeddings=[[0, 0, 0]], ids=["0"])
        assert "Insert of existing embedding ID: 0" not in caplog.text  # not
        assert "Add of existing embedding ID: 0" not in caplog.text  # not


class TestChromaDbCollection(unittest.TestCase):
    chroma_settings = {"allow_reset": True}
    chroma_config = ChromaDbConfig(chroma_settings=chroma_settings)
    app_config = AppConfig(collection_name=False, collect_metrics=False)
    app_with_settings = App(config=app_config, chromadb_config=chroma_config)

    def test_init_with_default_collection(self):
        """
        Test if the `App` instance is initialized with the correct default collection name.
        """
        app = App(config=AppConfig(collect_metrics=False))

        self.assertEqual(app.db.collection.name, "embedchain_store")

    def test_init_with_custom_collection(self):
        """
        Test if the `App` instance is initialized with the correct custom collection name.
        """
        config = AppConfig(collect_metrics=False)
        app = App(config=config)
        app.set_collection_name(name="test_collection")

        self.assertEqual(app.db.collection.name, "test_collection")

    def test_set_collection_name(self):
        """
        Test if the `App` collection is correctly switched using the `set_collection_name` method.
        """
        app = App(config=AppConfig(collect_metrics=False))
        app.set_collection_name("test_collection")

        self.assertEqual(app.db.collection.name, "test_collection")

    def test_changes_encapsulated(self):
        """
        Test that changes to one collection do not affect the other collection
        """
        # Start with a clean app
        self.app_with_settings.reset()

        app = App(config=AppConfig(collect_metrics=False))
        app.set_collection_name("test_collection_1")
        # Collection should be empty when created
        self.assertEqual(app.count(), 0)

        app.db.collection.add(embeddings=[0, 0, 0], ids=["0"])
        # After adding, should contain one item
        self.assertEqual(app.count(), 1)

        app.set_collection_name("test_collection_2")
        # New collection is empty
        self.assertEqual(app.count(), 0)

        # Adding to new collection should not effect existing collection
        app.db.collection.add(embeddings=[0, 0, 0], ids=["0"])
        app.set_collection_name("test_collection_1")
        # Should still be 1, not 2.
        self.assertEqual(app.count(), 1)

    def test_collections_are_persistent(self):
        """
        Test that a collection can be picked up later.
        """
        # Start with a clean app
        self.app_with_settings.reset()

        app = App(config=AppConfig(collect_metrics=False))
        app.set_collection_name("test_collection_1")
        app.db.collection.add(embeddings=[[0, 0, 0]], ids=["0"])
        del app

        app = App(config=AppConfig(collect_metrics=False))
        app.set_collection_name("test_collection_1")
        self.assertEqual(app.count(), 1)

    def test_parallel_collections(self):
        """
        Test that two apps can have different collections open in parallel.
        Switching the names will allow instant access to the collection of
        the other app.
        """
        # Start clean
        self.app_with_settings.reset()

        # Create two apps
        app1 = App(AppConfig(collection_name="test_collection_1", collect_metrics=False))
        app2 = App(AppConfig(collection_name="test_collection_2", collect_metrics=False))

        # app2 has been created last, but adding to app1 will still write to collection 1.
        app1.db.collection.add(embeddings=[0, 0, 0], ids=["0"])
        self.assertEqual(app1.db.count(), 1)
        self.assertEqual(app2.db.count(), 0)

        # Add data
        app1.db.collection.add(embeddings=[[0, 0, 0], [1, 1, 1]], ids=["1", "2"])
        app2.db.collection.add(embeddings=[0, 0, 0], ids=["0"])

        # Swap names and test
        app1.set_collection_name("test_collection_2")
        self.assertEqual(app1.count(), 1)
        app2.set_collection_name("test_collection_1")
        self.assertEqual(app2.count(), 3)

    def test_ids_share_collections(self):
        """
        Different ids should still share collections.
        """
        # Start clean
        self.app_with_settings.reset()

        # Create two apps
        app1 = App(AppConfig(id="new_app_id_1", collect_metrics=False))
        app1.set_collection_name("one_collection")
        app2 = App(AppConfig(id="new_app_id_2", collect_metrics=False))
        app2.set_collection_name("one_collection")

        # Add data
        app1.db.collection.add(embeddings=[[0, 0, 0], [1, 1, 1]], ids=["0", "1"])
        app2.db.collection.add(embeddings=[0, 0, 0], ids=["2"])

        # Both should have the same collection
        self.assertEqual(app1.count(), 3)
        self.assertEqual(app2.count(), 3)

    def test_reset(self):
        """
        Resetting should hit all collections and ids.
        """
        # Start clean
        self.app_with_settings.reset()

        # Create four apps.
        # app1, which we are about to reset, shares an app with one, and an id with the other, none with the last.
        app1 = App(AppConfig(id="new_app_id_1", collect_metrics=False), chromadb_config=self.chroma_config)
        app1.set_collection_name("one_collection")
        app2 = App(AppConfig(id="new_app_id_2", collect_metrics=False))
        app2.set_collection_name("one_collection")
        app3 = App(AppConfig(id="new_app_id_1", collect_metrics=False))
        app3.set_collection_name("three_collection")
        app4 = App(AppConfig(id="new_app_id_4", collect_metrics=False))
        app4.set_collection_name("four_collection")

        # Each one of them get data
        app1.db.collection.add(embeddings=[0, 0, 0], ids=["1"])
        app2.db.collection.add(embeddings=[0, 0, 0], ids=["2"])
        app3.db.collection.add(embeddings=[0, 0, 0], ids=["3"])
        app4.db.collection.add(embeddings=[0, 0, 0], ids=["4"])

        # Resetting the first one should reset them all.
        app1.reset()

        # Reinstantiate app2-4, app1 doesn't have to be reinstantiated (PR #319)
        app2 = App(AppConfig(collection_name="one_collection", id="new_app_id_2", collect_metrics=False))
        app3 = App(AppConfig(collection_name="three_collection", id="new_app_id_3", collect_metrics=False))
        app4 = App(AppConfig(collection_name="four_collection", id="new_app_id_3", collect_metrics=False))

        # All should be empty
        self.assertEqual(app1.count(), 0)
        self.assertEqual(app2.count(), 0)
        self.assertEqual(app3.count(), 0)
        self.assertEqual(app4.count(), 0)
