"""
Tests for LocalFileStorage (e4s_cl/cf/storage/local_file.py).

These tests exercise the storage layer in isolation using a temporary
directory so they are portable across all environments.  Nothing here
depends on an MPI installation, container runtime, or network access.
"""

import os
import shutil
import tempfile

import tests
from e4s_cl.cf.storage.local_file import LocalFileStorage


class LocalFileStorageCRUDTest(tests.TestCase):
    """Basic create/read/update/delete operations on a temporary storage."""

    def setUp(self):
        self._tmpdir = tempfile.mkdtemp(prefix='e4scl_storage_test_')
        self.storage = LocalFileStorage('test', self._tmpdir)

    def tearDown(self):
        self.storage.disconnect_database()
        shutil.rmtree(self._tmpdir, ignore_errors=True)

    # ------------------------------------------------------------------
    # insert / get / count
    # ------------------------------------------------------------------

    def test_insert_returns_record_with_data(self):
        record = self.storage.insert({'name': 'alpha'}, table_name='T')
        self.assertEqual(record['name'], 'alpha')

    def test_get_by_eid_returns_record(self):
        record = self.storage.insert({'name': 'beta'}, table_name='T')
        found = self.storage.get(record.eid, table_name='T')
        self.assertIsNotNone(found)
        self.assertEqual(found['name'], 'beta')

    def test_get_by_dict_key(self):
        self.storage.insert({'color': 'red'}, table_name='T')
        found = self.storage.get({'color': 'red'}, table_name='T')
        self.assertIsNotNone(found)
        self.assertEqual(found['color'], 'red')

    def test_get_missing_returns_none(self):
        result = self.storage.get({'x': 'absent'}, table_name='T')
        self.assertIsNone(result)

    def test_count_empty(self):
        self.assertEqual(self.storage.count(table_name='T'), 0)

    def test_count_after_inserts(self):
        self.storage.insert({'v': 1}, table_name='T')
        self.storage.insert({'v': 2}, table_name='T')
        self.assertEqual(self.storage.count(table_name='T'), 2)

    # ------------------------------------------------------------------
    # search
    # ------------------------------------------------------------------

    def test_search_all_returns_all_records(self):
        self.storage.insert({'val': 'a'}, table_name='T')
        self.storage.insert({'val': 'b'}, table_name='T')
        results = self.storage.search(table_name='T')
        self.assertEqual(len(results), 2)

    def test_search_with_filter(self):
        self.storage.insert({'color': 'red'}, table_name='T')
        self.storage.insert({'color': 'blue'}, table_name='T')
        results = self.storage.search(keys={'color': 'red'}, table_name='T')
        self.assertEqual(len(results), 1)
        self.assertEqual(results[0]['color'], 'red')

    def test_search_no_match_returns_empty(self):
        self.storage.insert({'color': 'red'}, table_name='T')
        results = self.storage.search(keys={'color': 'green'}, table_name='T')
        self.assertEqual(results, [])

    # ------------------------------------------------------------------
    # contains
    # ------------------------------------------------------------------

    def test_contains_true(self):
        self.storage.insert({'id': 'x'}, table_name='T')
        self.assertTrue(self.storage.contains({'id': 'x'}, table_name='T'))

    def test_contains_false(self):
        self.assertFalse(self.storage.contains({'id': 'y'}, table_name='T'))

    def test_contains_none_returns_false(self):
        self.assertFalse(self.storage.contains(None, table_name='T'))

    # ------------------------------------------------------------------
    # update
    # ------------------------------------------------------------------

    def test_update_changes_value(self):
        self.storage.insert({'n': 'old'}, table_name='T')
        self.storage.update({'n': 'new'}, {'n': 'old'}, table_name='T')
        self.assertTrue(self.storage.contains({'n': 'new'}, table_name='T'))
        self.assertFalse(self.storage.contains({'n': 'old'}, table_name='T'))

    # ------------------------------------------------------------------
    # remove / purge
    # ------------------------------------------------------------------

    def test_remove_deletes_matching_records(self):
        self.storage.insert({'kind': 'keep'}, table_name='T')
        self.storage.insert({'kind': 'delete'}, table_name='T')
        self.storage.remove({'kind': 'delete'}, table_name='T')
        self.assertFalse(self.storage.contains({'kind': 'delete'}, table_name='T'))
        self.assertTrue(self.storage.contains({'kind': 'keep'}, table_name='T'))

    def test_purge_clears_all_records(self):
        self.storage.insert({'a': 1}, table_name='T')
        self.storage.insert({'a': 2}, table_name='T')
        self.storage.purge(table_name='T')
        self.assertEqual(self.storage.count(table_name='T'), 0)


class LocalFileStorageKeyValueTest(tests.TestCase):
    """Tests for the dict-like key/value interface."""

    def setUp(self):
        self._tmpdir = tempfile.mkdtemp(prefix='e4scl_kv_test_')
        self.storage = LocalFileStorage('kv', self._tmpdir)

    def tearDown(self):
        self.storage.disconnect_database()
        shutil.rmtree(self._tmpdir, ignore_errors=True)

    def test_setitem_and_getitem(self):
        self.storage['mykey'] = 'myvalue'
        self.assertEqual(self.storage['mykey'], 'myvalue')

    def test_overwrite_existing_key(self):
        self.storage['k'] = 'v1'
        self.storage['k'] = 'v2'
        self.assertEqual(self.storage['k'], 'v2')

    def test_contains_present(self):
        self.storage['present'] = True
        self.assertIn('present', self.storage)

    def test_contains_absent(self):
        self.assertNotIn('absent', self.storage)

    def test_missing_key_raises_key_error(self):
        with self.assertRaises(KeyError):
            _ = self.storage['no_such_key']

    def test_delitem(self):
        self.storage['k'] = 'v'
        del self.storage['k']
        self.assertNotIn('k', self.storage)

    def test_delitem_missing_raises_key_error(self):
        with self.assertRaises(KeyError):
            del self.storage['nope']


class LocalFileStorageFilesystemTest(tests.TestCase):
    """Tests for filesystem helpers."""

    def setUp(self):
        self._tmpdir = tempfile.mkdtemp(prefix='e4scl_fs_test_')
        self.storage = LocalFileStorage('fs', self._tmpdir)

    def tearDown(self):
        self.storage.disconnect_database()
        shutil.rmtree(self._tmpdir, ignore_errors=True)

    def test_is_writable_for_temp_dir(self):
        """A freshly created temp directory must be writable."""
        self.assertTrue(self.storage.is_writable())

    def test_connect_creates_prefix_if_missing(self):
        """connect_filesystem() should create the prefix directory if absent."""
        sub = os.path.join(self._tmpdir, 'new_sub')
        storage = LocalFileStorage('sub', sub)
        self.assertFalse(os.path.isdir(sub))
        storage.connect_filesystem()
        self.assertTrue(os.path.isdir(sub))
        storage.disconnect_database()
