import random
import string

import pytest
from filip.models.ngsi_v2.iot import DeviceAttribute
from pydantic_core import ValidationError


class TestEntityOperations:
    def test_valid_entity(self, parser, schema_identifier, entity_instance):
        _ = parser.create_context_entity(
            instance=entity_instance, identifier=schema_identifier
        )

    def test_missing_field_in_instance_with_default(
        self, parser, schema_identifier, entity_instance
    ):
        # test if default value is used correctly
        entity = parser.create_context_entity(
            {
                key: val
                for key, val in entity_instance.items()
                if key != "statAttr"
            },
            identifier=schema_identifier,
        )
        # check if static attributes default value is used
        assert hasattr(entity, "statAttr")
        assert entity.statAttr.value == "default_value"

    def test_exception_handling(
        self, parser, entity_instance, schema_identifier
    ):
        """Test to check if all invalid inputs are caught as expected"""

        # Test instancing an entity with value for attribute not defined in scheme
        instance = entity_instance.copy()
        instance.update({"newAttr": 25})

        with pytest.raises(ValueError):
            parser.create_context_entity(instance, schema_identifier)

        instance.pop("newAttr")

        # Test validation of values (le and ge)
        # value is out of range as defined in schema (0- 100)
        instance["attr"] = 105
        with pytest.raises(ValidationError):
            parser.create_context_entity(instance, schema_identifier)


class TestDeviceOperations:
    def test_valid_device(self, parser, schema_identifier, device_instance):
        _ = parser.create_device(
            instance=device_instance, identifier=schema_identifier
        )

    def test_invalid_entity_type(
        self, parser, schema_identifier, device_instance
    ):
        instance = device_instance.copy()
        instance["entity_type"] = "NotAThing"
        with pytest.raises(ValidationError):
            _ = parser.create_device(
                instance=instance, identifier=schema_identifier
            )

    def test_attr_types_with_same_as_schema(
        self, parser, schema_identifier, device_instance
    ):
        _ = parser.create_device(
            identifier=schema_identifier,
            instance=device_instance,
            attr_types={
                "statAttr": "StaticDeviceAttribute",
                "attr": "DeviceAttribute",
                "command": "DeviceCommand",
            },
        )

    def test_attr_types_overwriting_schema(
        self, parser, schema_identifier, device_instance
    ):
        device = parser.create_device(
            identifier=schema_identifier,
            instance=device_instance,
            attr_types={
                "statAttr": "DeviceAttribute",
                "attr": "DeviceAttribute",
                "command": "DeviceCommand",
            },
        )
        attr = device.get_attribute("statAttr")
        assert isinstance(attr, DeviceAttribute)

    def test_attr_types_with_invalid_value(
        self, parser, schema_identifier, device_instance
    ):
        attr_types = {
            "statAttr": "StaticAttribute",  # Should be StaticDeviceAttribute
            "attr": "DeviceAttribute",
            "command": "DeviceCommand",
        }
        with pytest.raises(ValueError):
            _ = parser.create_device(
                identifier=schema_identifier,
                instance=device_instance,
                attr_types=attr_types,
            )

    def test_invalid_entity_name(
        self, parser, schema_identifier, device_instance
    ):
        instance = device_instance.copy()
        # Create an id that is too long
        instance["entity_name"] = "".join(
            random.choices(string.ascii_uppercase + string.digits, k=300)
        )
        with pytest.raises(ValidationError):
            _ = parser.create_device(
                instance=instance, identifier=schema_identifier
            )
        # Create an id that contains forbidden characters
        instance["entity_name"] = "\\"
        with pytest.raises(ValidationError):
            _ = parser.create_device(
                instance=instance, identifier=schema_identifier
            )

    def test_missing_field_in_device_instance(
        self, parser, schema_identifier, device_instance
    ):
        instance = {
            key: val
            for key, val in device_instance.items()
            if key != "statAttr"
        }
        # test if default value is used correctly
        device = parser.create_device(instance, schema_identifier)
        # check if attribute is existent and has the right value
        assert any(
            val for val in device.static_attributes if val.name == "statAttr"
        )
        assert device.static_attributes[0].value == "default_value"

    def test_attr_value_not_in_schema(
        self, parser, schema_identifier, device_instance
    ):
        """Test to check if all invalid inputs are caught as expected"""
        instance = device_instance.copy()
        # Test instancing a device with value for attribute not defined in scheme
        instance.update({"newAttr": 25})
        with pytest.raises(ValueError):
            parser.create_device(instance, schema_identifier)
