# (c) cavaliba.com - test_schema

import yaml

from django.test import override_settings
from django.test import TestCase
from django.test import TransactionTestCase
from django.urls import reverse
from django.core.cache import cache as cache_django
from django.core.management import call_command

from tests import helper

import app_home.cache as cache
from app_data.models import DataInstance, DataClass, DataSchema
from app_data.data import Instance

from app_data.schema import Schema


class TestDataSchema(TestCase):


    def setUp(self):
        cache.clear()

    # ---------------------------

    def test_create_schema(self):
        schema = helper.add_schema(
            classname='unittestschema',
            displayname='Unittest Schema',
            options={'icon': 'testicon', 'unittest_option': 'edit'},
            verbose=False
        )

        self.assertIsNotNone(schema)
        self.assertEqual(schema.displayname, 'Unittest Schema')
        self.assertEqual(schema.icon, 'testicon')
        self.assertNotIn('unittest_option', schema.options)


    # ---------------------------

    def test_listsall_schema(self):
        helper.add_schema(
            classname='unittestschema',
            displayname='Unittest Schema',
            options={'unittest_option': 'edit', 'icon': 'testicon'},
            verbose=False
        )

        schemas = Schema.listall()
        self.assertIs(len(schemas), 1)

    # ---------------------------

    def test_delete_schema(self):
        # Create a schema to delete
        schema = helper.add_schema(
            classname='deleteschema',
            displayname='Delete Test Schema',
            verbose=False
        )

        self.assertIsNotNone(schema)
        self.assertEqual(schema.displayname, 'Delete Test Schema')

        # Verify schema exists
        schema_check = Schema.from_name('deleteschema')
        self.assertIsNotNone(schema_check)

        # Delete the schema using add_schema with action='delete'
        helper.add_schema(
            classname='deleteschema',
            action='delete',
            verbose=False
        )

        # Verify schema is deleted (should return None)
        deleted_schema = Schema.from_name('deleteschema')
        self.assertIsNone(deleted_schema)

    # ---------------------------

    def test_schema_delete_classmethod(self):
        # Create a schema with 2 fields
        schema = helper.add_schema(
            classname='deletefieldsschema',
            displayname='Delete Fields Schema',
            field_definition={
                'field1': {'dataformat': 'string', 'displayname': 'Field 1'},
                'field2': {'dataformat': 'int', 'displayname': 'Field 2'}
            },
            verbose=False
        )

        self.assertIsNotNone(schema)
        self.assertEqual(len(schema.fields), 2)

        # Verify DataClass and DataSchema entries exist
        dataclass = DataClass.objects.filter(keyname='deletefieldsschema').first()
        self.assertIsNotNone(dataclass)

        dataschema_count = DataSchema.objects.filter(classname='deletefieldsschema').count()
        self.assertEqual(dataschema_count, 2)

        # Delete the schema using Schema.delete() classmethod
        Schema.delete('deletefieldsschema')

        # Verify schema is deleted (should return None)
        deleted_schema = Schema.from_name('deletefieldsschema')
        self.assertIsNone(deleted_schema)

        # Verify DataClass entry is deleted
        dataclass_after = DataClass.objects.filter(keyname='deletefieldsschema').first()
        self.assertIsNone(dataclass_after)

        # Verify DataSchema entries are deleted
        dataschema_count_after = DataSchema.objects.filter(classname='deletefieldsschema').count()
        self.assertEqual(dataschema_count_after, 0)

    # ---------------------------

    def test_schema_exists_classmethod(self):
        # Test that exists() returns False for non-existent schema
        self.assertFalse(Schema.exists('nonexistentschema'))
        self.assertFalse(Schema.exists(None))

        # Create a schema
        schema = helper.add_schema(
            classname='existsschema',
            displayname='Exists Test Schema',
            verbose=False
        )

        self.assertIsNotNone(schema)

        # Test that exists() returns True for existing schema
        self.assertTrue(Schema.exists('existsschema'))

        # Delete the schema
        Schema.delete('existsschema')

        # Test that exists() returns False after deletion
        self.assertFalse(Schema.exists('existsschema'))

    # ---------------------------

    def test_schema_disable_classmethod(self):
        # Create a schema with 2 fields
        schema = helper.add_schema(
            classname='disableschema',
            displayname='Disable Test Schema',
            field_definition={
                'field1': {'dataformat': 'string', 'displayname': 'Field 1'},
                'field2': {'dataformat': 'int', 'displayname': 'Field 2'}
            },
            verbose=False
        )

        self.assertIsNotNone(schema)
        self.assertEqual(len(schema.fields), 2)

        # Verify schema is enabled initially
        schema_before = Schema.from_name('disableschema')
        self.assertIsNotNone(schema_before)
        dataclass_before = DataClass.objects.filter(keyname='disableschema').first()
        self.assertIsNotNone(dataclass_before)
        self.assertTrue(dataclass_before.is_enabled)

        # Disable the schema using Schema.disable() classmethod
        Schema.disable('disableschema')

        # Verify schema still exists
        schema_after = Schema.from_name('disableschema')
        self.assertIsNotNone(schema_after)

        # Verify schema is disabled in the database
        dataclass_after = DataClass.objects.filter(keyname='disableschema').first()
        self.assertIsNotNone(dataclass_after)
        self.assertFalse(dataclass_after.is_enabled)

    # ---------------------------

    def test_update_schema(self):
        # Create a schema with 2 fields
        schema = helper.add_schema(
            classname='updateschema',
            displayname='Update Test Schema',
            field_definition={
                'name': {'dataformat': 'string', 'displayname': 'Name'},
                'email': {'dataformat': 'string', 'displayname': 'Email'}
            },
            verbose=False
        )

        self.assertIsNotNone(schema)
        self.assertEqual(schema.displayname, 'Update Test Schema')
        self.assertEqual(len(schema.fields), 2)
        self.assertIn('name', schema.fields)
        self.assertIn('email', schema.fields)

        # Verify schema exists with fields
        schema_check = Schema.from_name('updateschema')
        self.assertIsNotNone(schema_check)
        self.assertEqual(len(schema_check.fields), 2)

        # Add a field to the schema
        helper.add_schema_field(classname='updateschema', 
                fields={'phone': {'dataformat': 'string', 'displayname': 'Phone Number'}}, 
                verbose=False)

        # Verify schema now has 3 fields
        schema_updated = Schema.from_name('updateschema')
        self.assertEqual(len(schema_updated.fields), 3)
        self.assertIn('name', schema_updated.fields)
        self.assertIn('email', schema_updated.fields)
        self.assertIn('phone', schema_updated.fields)
        self.assertEqual(schema_updated.fields['phone']['displayname'], 'Phone Number')


    # ---------------------------

    def test_create_schema_with_fields(self):

        schema = helper.add_schema(
            classname='schemawithfields',
            displayname='Schema With Fields',
            field_definition={
                'name': {'dataformat': 'string', 'displayname': 'Name'},
                'age': {'dataformat': 'int', 'displayname': 'Age', 'description': 'Person age'},
                'email': {'dataformat': 'string', 'displayname': 'Email'}
            },
            verbose=False
        )

        self.assertIsNotNone(schema)
        self.assertEqual(schema.displayname, 'Schema With Fields')
        self.assertEqual(len(schema.fields), 3)
        self.assertIn('name', schema.fields)
        self.assertIn('age', schema.fields)
        self.assertIn('email', schema.fields)
        self.assertEqual(schema.fields['name']['dataformat'], 'string')
        self.assertEqual(schema.fields['age']['dataformat'], 'int')
        self.assertEqual(schema.fields['age']['displayname'], 'Age')

    # ---------------------------

    def test_add_schema_field_helper(self):
        
        schema = helper.add_schema(
            classname='fieldschema',
            displayname='Field Schema',
            verbose=False
        )

        self.assertIsNotNone(schema)
        self.assertEqual(len(schema.fields), 0)

        helper.add_schema_field(
            classname='fieldschema', 
            fields = {
                'name': {'dataformat': 'string', 'displayname': 'Name'},
                'count': {'dataformat': 'int', 'displayname': 'Count', 'description': 'Number of items'}
                }, 
            verbose=False)

        # Verify that the schema still exists and is accessible
        schema = Schema.from_name('fieldschema')
        self.assertIsNotNone(schema)
        self.assertEqual(schema.classname, 'fieldschema')
        self.assertEqual(schema.displayname, 'Field Schema')
        self.assertEqual(len(schema.fields), 2)


    # ---------------------------
    # instance
    # ---------------------------

    def test_empty_instance(self):
        i = Instance()
        self.assertIsNotNone(i)

    # ---------------------------

    def test_instance(self):
        # Create a schema with 2 fields
        schema = helper.add_schema(
            classname='instanceschema',
            displayname='Instance Test Schema',
            field_definition={
                'name': {'dataformat': 'string', 'displayname': 'Name'},
                'email': {'dataformat': 'string', 'displayname': 'Email'}
            },
            verbose=False
        )

        self.assertIsNotNone(schema)
        self.assertEqual(len(schema.fields), 2)

        # Create an instance with field values
        helper.add_instance(
            classname='instanceschema',
            keyname='instance01',
            fields={
                'name': 'John Doe',
                'email': 'john@example.com'
            },
            verbose=False
        )

        # Get the instance and check field values
        instance = Instance.from_keyname(classname='instanceschema', keyname='instance01')
        self.assertIsNotNone(instance)
        self.assertIn('name', instance.fields)
        self.assertEqual(instance.fields['name'].get_first_value(), 'John Doe')
        self.assertEqual(instance.fields['email'].get_first_value(), 'john@example.com')


    # ---------------------------
    # keyname_mode
    # ---------------------------

    def test_keyname_mode_edit(self):
        schema = helper.add_schema(
            classname='unittestschema',
            displayname='Unittest Schema',
            options={'icon': 'testicon', 'keyname_mode': 'edit'},
            verbose=False
        )

        self.assertIsNotNone(schema)
        self.assertIn('keyname_mode', schema.options)
        self.assertEqual(schema.keyname_mode, 'edit')

    # ---------------------------

    def test_keyname_mode_auto(self):
        schema = helper.add_schema(
            classname='unittestschema',
            displayname='Unittest Schema',
            options={'icon': 'testicon', 'keyname_mode': 'auto'},
            verbose=False
        )

        self.assertIsNotNone(schema)
        self.assertIn('keyname_mode', schema.options)
        self.assertEqual(schema.keyname_mode, 'auto')

    # ---------------------------

    def test_keyname_mode_default(self):
        schema = helper.add_schema(
            classname='unittestschema',
            displayname='Unittest Schema',
            verbose=False
        )

        self.assertIsNotNone(schema)
        self.assertEqual(schema.keyname_mode, 'edit')

    # ---------------------------

    def test_keyname_mode_invalid(self):
        schema = helper.add_schema(
            classname='unittestschema',
            displayname='Unittest Schema',
            options={'icon': 'testicon', 'keyname_mode': 'invalid'},
            verbose=False
        )

        self.assertIsNotNone(schema)
        self.assertEqual(schema.keyname_mode, 'edit')

    # ---------------------------

    def test_keyname_mode_auto_result(self):
        helper.add_schema(
            classname='unittestschema',
            displayname='Unittest Schema',
            options={'icon': 'testicon', 'keyname_mode': 'auto'},
            verbose=False
        )

        # Create instances with auto keyname mode
        helper.add_instance('unittestschema', keyname='utest01', verbose=False)
        helper.add_instance('unittestschema', verbose=False)

        schema = Schema.from_name('unittestschema')
        self.assertIsNotNone(schema)
        self.assertIn('keyname_mode', schema.options)
        self.assertEqual(schema.keyname_mode, 'auto')

        inst1 = Instance.from_keyname(classname='unittestschema', keyname='utest01')
        self.assertIsNone(inst1)

        qs = DataInstance.objects.filter(classname='unittestschema')
        self.assertEqual(len(qs), 2)

        for obj in qs:
            k = obj.keyname
            self.assertEqual(len(k), 36)



    # ---------------------------
    # Static methods
    # ---------------------------

    def test_get_empty_field_dict(self):
        field_dict = Schema.get_empty_field_dict()

        # Verify the returned dict has all required keys and correct defaults
        self.assertIsInstance(field_dict, dict)
        self.assertEqual(field_dict['displayname'], '')
        self.assertTrue(field_dict['is_enabled'])
        self.assertEqual(field_dict['dataformat'], 'string')
        self.assertEqual(field_dict['dataformat_ext'], '')
        self.assertEqual(field_dict['order'], 100)
        self.assertEqual(field_dict['page'], 'Default')
        self.assertEqual(field_dict['cardinal_min'], 0)
        self.assertEqual(field_dict['cardinal_max'], 1)
        self.assertEqual(field_dict['default_value'], '')



    def test_update_field_from_dict(self):
        # Create initial field dict with all fields
        field_dict = {
            'displayname': 'Original Name',
            'is_enabled': True,
            'dataformat': 'string',
            'dataformat_ext': '',
            'order': 10,
            'page': 'Default',
            'cardinal_min': 0,
            'cardinal_max': 1,
            'default_value': '',
        }

        # Test 1: No changes - should return False
        update_dict = {
            'displayname': 'Original Name',
            'dataformat': 'string',
        }
        changed = Schema.update_field_from_dict(field_dict, update_dict)
        self.assertFalse(changed)
        self.assertEqual(field_dict['displayname'], 'Original Name')
        self.assertEqual(field_dict['dataformat'], 'string')
        self.assertEqual(field_dict['order'], 10)

        # Test 2: Update multiple fields - should return True
        update_dict = {
            'displayname': 'Updated Name',
            'order': 20,
            'page': 'CustomPage',
            'cardinal_max': 5,
        }
        changed = Schema.update_field_from_dict(field_dict, update_dict)
        self.assertTrue(changed)
        self.assertEqual(field_dict['displayname'], 'Updated Name')
        self.assertEqual(field_dict['order'], 20)
        self.assertEqual(field_dict['page'], 'CustomPage')
        self.assertEqual(field_dict['cardinal_max'], 5)
        # Verify unchanged fields stay the same
        self.assertTrue(field_dict['is_enabled'])
        self.assertEqual(field_dict['dataformat'], 'string')
        self.assertEqual(field_dict['cardinal_min'], 0)
        self.assertEqual(field_dict['default_value'], '')

    # ---------------------------
    # keyname_label
    # ---------------------------

    # ---------------------------
    # displayname_label
    # ---------------------------