''' Testing API: Propagate changes through references in OSS. '''

from apps.oss.models import OperationSchema, OperationType
from apps.rsform.models import Constituenta, CstType, RSForm
from shared.EndpointTester import EndpointTester, decl_endpoint


class ReferencePropagationTestCase(EndpointTester):
    ''' Test propagation through references in OSS. '''

    def setUp(self):
        super().setUp()

        self.owned = OperationSchema.create(
            title='Test',
            alias='T1',
            owner=self.user
        )
        self.owned_id = self.owned.model.pk

        self.ks1 = RSForm.create(
            alias='KS1',
            title='Test1',
            owner=self.user
        )
        self.ks1X1 = self.ks1.insert_last('X1', convention='KS1X1')
        self.ks1X2 = self.ks1.insert_last('X2', convention='KS1X2')
        self.ks1D1 = self.ks1.insert_last('D1', definition_formal='X1 X2', convention='KS1D1')

        self.ks2 = RSForm.create(
            alias='KS2',
            title='Test2',
            owner=self.user
        )
        self.ks2X1 = self.ks2.insert_last('X1', convention='KS2X1')
        self.ks2X2 = self.ks2.insert_last('X2', convention='KS2X2')
        self.ks2S1 = self.ks2.insert_last(
            alias='S1',
            definition_formal=r'ℬ(X1)',
            convention='KS2S1'
        )

        self.operation1 = self.owned.create_operation(
            alias='1',
            operation_type=OperationType.INPUT,
            result=self.ks1.model
        )
        self.operation2 = self.owned.create_operation(
            alias='2',
            operation_type=OperationType.INPUT,
            result=self.ks2.model
        )
        self.operation3 = self.owned.create_reference(self.operation1)

        self.operation4 = self.owned.create_operation(
            alias='4',
            operation_type=OperationType.SYNTHESIS
        )
        self.owned.set_arguments(self.operation4.pk, [self.operation1, self.operation2])
        self.owned.set_substitutions(self.operation4.pk, [{
            'original': self.ks1X1,
            'substitution': self.ks2S1
        }])
        self.owned.execute_operation(self.operation4)
        self.operation4.refresh_from_db()
        self.ks4 = RSForm(self.operation4.result)
        self.ks4X1 = Constituenta.objects.get(as_child__parent_id=self.ks1X2.pk)
        self.ks4S1 = Constituenta.objects.get(as_child__parent_id=self.ks2S1.pk)
        self.ks4D1 = Constituenta.objects.get(as_child__parent_id=self.ks1D1.pk)
        self.ks4D2 = self.ks4.insert_last(
            alias='D2',
            definition_formal=r'X1 X2 X3 S1 D1',
            convention='KS4D2'
        )

        self.operation5 = self.owned.create_operation(
            alias='5',
            operation_type=OperationType.SYNTHESIS
        )
        self.owned.set_arguments(self.operation5.pk, [self.operation4, self.operation3])
        self.owned.set_substitutions(self.operation5.pk, [{
            'original': self.ks4X1,
            'substitution': self.ks1X1
        }])
        self.owned.execute_operation(self.operation5)
        self.operation5.refresh_from_db()
        self.ks5 = RSForm(self.operation5.result)
        self.ks5D4 = self.ks5.insert_last(
            alias='D4',
            definition_formal=r'X1 X2 X3 S1 D1 D2 D3',
            convention='KS5D4'
        )

        self.layout_data = [
            {'nodeID': 'o' + str(self.operation1.pk), 'x': 0, 'y': 0, 'width': 150, 'height': 40},
            {'nodeID': 'o' + str(self.operation2.pk), 'x': 0, 'y': 0, 'width': 150, 'height': 40},
            {'nodeID': 'o' + str(self.operation3.pk), 'x': 0, 'y': 0, 'width': 150, 'height': 40},
            {'nodeID': 'o' + str(self.operation4.pk), 'x': 0, 'y': 0, 'width': 150, 'height': 40},
            {'nodeID': 'o' + str(self.operation5.pk), 'x': 0, 'y': 0, 'width': 150, 'height': 40}
        ]
        layout = OperationSchema.layoutQ(self.owned_id)
        layout.data = self.layout_data
        layout.save()


    def test_reference_creation(self):
        ''' Test reference creation. '''
        self.assertEqual(self.operation1.result, self.operation3.result)



    # @decl_endpoint('/api/oss/{item}/delete-reference', method='patch')
    # def test_delete_reference_propagation(self):
    #     ''' Test propagation when deleting a reference operation. '''
    #     schema_cached = OperationSchemaCached(self.schema2)
    #     # Ensure reference exists
    #     self.assertIn(self.reference.pk, schema_cached.cache.operation_by_id)
    #     # Delete the reference
    #     schema_cached.delete_reference(self.reference.pk)
    #     # Reference should be deleted
    #     with self.assertRaises(Reference.DoesNotExist):
    #         Reference.objects.get(pk=self.reference.pk)
    #     # Operation2 should still exist
    #     self.assertTrue(Operation.objects.filter(pk=self.op2.pk).exists())


    # @decl_endpoint('/api/oss/{item}/set-arguments', method='patch')
    # def test_set_arguments_propagation(self):
    #     ''' Test propagation when setting arguments for a reference. '''
    #     schema_cached = OperationSchemaCached(self.schema2)
    #     # Add op1 as argument to op2
    #     schema_cached.set_arguments(self.op2.pk, [self.op1])
    #     op2 = Operation.objects.get(pk=self.op2.pk)
    #     args = list(op2.getQ_arguments())
    #     self.assertEqual(len(args), 1)
    #     self.assertEqual(args[0].argument, self.op1)


    # @decl_endpoint('/api/oss/{item}/delete-operation', method='patch')
    # def test_delete_operation_with_reference(self):
    #     ''' Test propagation when deleting an operation that is referenced. '''
    #     schema_cached = OperationSchemaCached(self.schema1)
    #     # op1 is referenced by reference
    #     self.assertEqual(self.reference.getQ_reference_target(), self.op1)
    #     # Delete op1
    #     schema_cached.delete_operation(self.op1.pk)
    #     # op1 should be deleted
    #     with self.assertRaises(Operation.DoesNotExist):
    #         Operation.objects.get(pk=self.op1.pk)
    #     # Reference should be deleted as well
    #     self.assertFalse(Reference.objects.filter(pk=self.reference.pk).exists())


    # @decl_endpoint('/api/oss/{item}/add-constituent', method='patch')
    # def test_add_constituent_propagation(self):
    #     ''' Test propagation when adding a constituent to a referenced schema. '''
    #     # Add a new constituent to schema1 (referenced by op1, which is referenced by reference)
    #     new_cst = Constituenta.objects.create(
    #         schema=self.schema1, alias='cst_new', title='New Constituenta', cst_type=CstType.ATTRIBUTE
    #     )
    #     # Simulate propagation: after adding, the reference should still be valid and schema1 should have the new cst
    #     self.assertTrue(Constituenta.objects.filter(pk=new_cst.pk, schema=self.schema1).exists())
    #     # The reference's target operation's result should include the new constituent
    #     op1 = Operation.objects.get(pk=self.op1.pk)
    #     self.assertEqual(op1.result, self.schema1)
    #     self.assertIn(new_cst, Constituenta.objects.filter(schema=op1.result))


    # @decl_endpoint('/api/oss/{item}/remove-constituent', method='patch')
    # def test_remove_constituent_propagation(self):
    #     ''' Test propagation when removing a constituent from a referenced schema. '''
    #     # Remove cst2 from schema1
    #     self.cst2.delete()
    #     # The reference's target operation's result should not include cst2
    #     op1 = Operation.objects.get(pk=self.op1.pk)
    #     self.assertEqual(op1.result, self.schema1)
    #     self.assertNotIn(self.cst2, Constituenta.objects.filter(schema=op1.result))
    #     # Reference should still be valid
    #     self.assertEqual(self.reference.getQ_reference_target(), self.op1)


    # @decl_endpoint('/api/oss/{item}/add-constituent-to-referenced-schema', method='patch')
    # def test_propagation_to_multiple_references(self):
    #     ''' Test propagation when a schema is referenced by multiple references and constituents are added. '''
    #     # Create another reference to op1
    #     reference2 = Reference.objects.create(
    #         alias='ref2', title='Reference 2', type=OperationType.REFERENCE, result=self.schema2
    #     )
    #     reference2.setQ_reference_target(self.op1)
    #     reference2.save()
    #     # Add a new constituent to schema1
    #     new_cst = Constituenta.objects.create(
    #         schema=self.schema1, alias='cst_multi', title='Multi Constituenta', cst_type=CstType.ATTRIBUTE
    #     )
    #     # Both references should still be valid and op1's result should include the new constituent
    #     op1 = Operation.objects.get(pk=self.op1.pk)
    #     self.assertIn(new_cst, Constituenta.objects.filter(schema=op1.result))
    #     self.assertEqual(self.reference.getQ_reference_target(), self.op1)
    #     self.assertEqual(reference2.getQ_reference_target(), self.op1)
