import pytest
import tempfile
import yaml
from pathlib import Path

from atomingest.core.loader import SchemaLoader
from atomsql.orm import fields, Model
from atomsql.utils.exceptions import ImproperlyConfigured


class TestSchemaLoader:
    """Test suite for SchemaLoader class."""

    @pytest.fixture
    def loader(self):
        """Create a fresh SchemaLoader instance for each test."""
        return SchemaLoader()

    @pytest.fixture
    def temp_yaml_file(self):
        """Create a temporary YAML file for testing."""
        temp_file = tempfile.NamedTemporaryFile(mode="w", suffix=".yaml", delete=False)
        yield temp_file
        temp_file.close()
        Path(temp_file.name).unlink(missing_ok=True)

    def test_field_mapping_contains_all_expected_fields(self, loader):
        """Test that FIELD_MAPPING contains all expected field types."""
        expected_fields = [
            "Field",
            "BooleanField",
            "IntegerField",
            "TimestampField",
            "StringField",
            "FloatField",
            "CharField",
            "TextField",
            "JSONField",
            "EmailField",
            "URLField",
            "DateField",
            "DateTimeField",
            "DateTimeTZField",
        ]
        for field_name in expected_fields:
            assert field_name in loader.FIELD_MAPPING
            assert callable(loader.FIELD_MAPPING[field_name])

    def test_load_from_yaml_valid_simple_config(self, loader, temp_yaml_file):
        """Test loading a valid simple YAML configuration."""
        config = {
            "target_table": "users",
            "schema": {
                "id": "IntegerField",
                "name": "StringField",
                "email": "EmailField",
            },
        }
        yaml.dump(config, temp_yaml_file)
        temp_yaml_file.flush()

        result = loader.load_from_yaml(temp_yaml_file.name)

        assert result["target_table"] == "users"
        assert "id" in result["schema"]
        assert "name" in result["schema"]
        assert "email" in result["schema"]
        assert isinstance(result["schema"]["id"], fields.IntegerField)
        assert isinstance(result["schema"]["name"], fields.StringField)
        assert isinstance(result["schema"]["email"], fields.EmailField)
        assert result["config"] == config

    def test_load_from_yaml_with_field_options(self, loader, temp_yaml_file):
        """Test loading YAML configuration with field options."""
        config = {
            "target_table": "products",
            "schema": {
                "id": {"type": "IntegerField", "unique": True},
                "name": {"type": "CharField", "max_length": 255, "nullable": False},
                "price": {"type": "FloatField", "nullable": False},
            },
        }
        yaml.dump(config, temp_yaml_file)
        temp_yaml_file.flush()

        result = loader.load_from_yaml(temp_yaml_file.name)

        assert result["target_table"] == "products"
        assert isinstance(result["schema"]["id"], fields.IntegerField)
        assert isinstance(result["schema"]["name"], fields.CharField)
        assert isinstance(result["schema"]["price"], fields.FloatField)

    def test_load_from_yaml_file_not_found(self, loader):
        """Test that FileNotFoundError is raised for non-existent file."""
        with pytest.raises(FileNotFoundError) as exc_info:
            loader.load_from_yaml("/path/to/nonexistent/file.yaml")
        assert "Configuration file not found" in str(exc_info.value)

    def test_load_from_yaml_invalid_yaml(self, loader, temp_yaml_file):
        """Test that ImproperlyConfigured is raised for invalid YAML."""
        temp_yaml_file.write("invalid: yaml: content: [[[")
        temp_yaml_file.flush()

        with pytest.raises(ImproperlyConfigured) as exc_info:
            loader.load_from_yaml(temp_yaml_file.name)
        assert "Error parsing YAML file" in str(exc_info.value)

    def test_load_from_yaml_non_dict_config(self, loader, temp_yaml_file):
        """Test that ImproperlyConfigured is raised when YAML is not a dict."""
        yaml.dump(["list", "instead", "of", "dict"], temp_yaml_file)
        temp_yaml_file.flush()

        with pytest.raises(ImproperlyConfigured) as exc_info:
            loader.load_from_yaml(temp_yaml_file.name)
        assert "YAML configuration must be a dictionary" in str(exc_info.value)

    def test_load_from_yaml_missing_target_table(self, loader, temp_yaml_file):
        """Test that ImproperlyConfigured is raised when target_table is missing."""
        config = {"schema": {"id": "IntegerField"}}
        yaml.dump(config, temp_yaml_file)
        temp_yaml_file.flush()

        with pytest.raises(ImproperlyConfigured) as exc_info:
            loader.load_from_yaml(temp_yaml_file.name)
        assert "Missing 'target_table'" in str(exc_info.value)

    def test_load_from_yaml_missing_schema(self, loader, temp_yaml_file):
        """Test that ImproperlyConfigured is raised when schema is missing."""
        config = {"target_table": "users"}
        yaml.dump(config, temp_yaml_file)
        temp_yaml_file.flush()

        with pytest.raises(ImproperlyConfigured) as exc_info:
            loader.load_from_yaml(temp_yaml_file.name)
        assert "Missing 'schema'" in str(exc_info.value)

    def test_load_from_yaml_schema_not_dict(self, loader, temp_yaml_file):
        """Test that ImproperlyConfigured is raised when schema is not a dict."""
        config = {"target_table": "users", "schema": ["not", "a", "dict"]}
        yaml.dump(config, temp_yaml_file)
        temp_yaml_file.flush()

        with pytest.raises(ImproperlyConfigured) as exc_info:
            loader.load_from_yaml(temp_yaml_file.name)
        assert "'schema' must be a dictionary" in str(exc_info.value)

    def test_parse_schema_with_string_type(self, loader):
        """Test _parse_schema with simple string field types."""
        raw_schema = {
            "id": "IntegerField",
            "name": "StringField",
            "active": "BooleanField",
        }

        result, validation_configs = loader._parse_schema(raw_schema)

        assert len(result) == 3
        assert isinstance(result["id"], fields.IntegerField)
        assert isinstance(result["name"], fields.StringField)
        assert isinstance(result["active"], fields.BooleanField)
        assert validation_configs == {}  # No validation rules

    def test_parse_schema_with_dict_type(self, loader):
        """Test _parse_schema with dict field definitions."""
        raw_schema = {
            "id": {"type": "IntegerField", "unique": True},
            "email": {"type": "EmailField", "nullable": False},
        }

        result, validation_configs = loader._parse_schema(raw_schema)

        assert len(result) == 2
        assert isinstance(result["id"], fields.IntegerField)
        assert isinstance(result["email"], fields.EmailField)
        assert validation_configs == {}  # No validation rules

    def test_parse_schema_unknown_field_type(self, loader):
        """Test that ImproperlyConfigured is raised for unknown field type."""
        raw_schema = {"invalid_field": "UnknownFieldType"}

        with pytest.raises(ImproperlyConfigured) as exc_info:
            loader._parse_schema(raw_schema)
        assert "Unknown field type 'UnknownFieldType'" in str(exc_info.value)

    def test_parse_schema_missing_type_in_dict(self, loader):
        """Test that ImproperlyConfigured is raised when type is missing in dict definition."""
        raw_schema = {"field_name": {"null": False, "primary_key": True}}

        with pytest.raises(ImproperlyConfigured) as exc_info:
            loader._parse_schema(raw_schema)
        assert "missing 'type' definition" in str(exc_info.value)

    def test_parse_schema_invalid_field_definition(self, loader):
        """Test that ImproperlyConfigured is raised for invalid field definition."""
        raw_schema = {
            "field_name": 123  # Invalid type (not string or dict)
        }

        with pytest.raises(ImproperlyConfigured) as exc_info:
            loader._parse_schema(raw_schema)
        assert "Invalid definition for field" in str(exc_info.value)

    def test_parse_schema_invalid_field_options(self, loader):
        """Test that ImproperlyConfigured is raised for invalid field initialization."""
        raw_schema = {"id": {"type": "IntegerField", "invalid_option": "value"}}

        with pytest.raises(ImproperlyConfigured) as exc_info:
            loader._parse_schema(raw_schema)
        assert "Error initializing field" in str(exc_info.value)

    def test_load_from_yaml_all_field_types(self, loader, temp_yaml_file):
        """Test loading configuration with all supported field types."""
        config = {
            "target_table": "test_table",
            "schema": {
                "field1": "Field",
                "field2": "BooleanField",
                "field3": "IntegerField",
                "field4": "TimestampField",
                "field5": "StringField",
                "field6": "FloatField",
                "field7": {"type": "CharField", "max_length": 100},
                "field8": "TextField",
                "field9": "JSONField",
                "field10": "EmailField",
                "field11": "URLField",
                "field12": "DateField",
                "field13": "DateTimeField",
                "field14": "DateTimeTZField",
            },
        }
        yaml.dump(config, temp_yaml_file)
        temp_yaml_file.flush()

        result = loader.load_from_yaml(temp_yaml_file.name)

        assert result["target_table"] == "test_table"
        assert len(result["schema"]) == 14
        assert isinstance(result["schema"]["field1"], fields.Field)
        assert isinstance(result["schema"]["field2"], fields.BooleanField)
        assert isinstance(result["schema"]["field3"], fields.IntegerField)
        assert isinstance(result["schema"]["field4"], fields.TimeStampField)
        assert isinstance(result["schema"]["field5"], fields.StringField)
        assert isinstance(result["schema"]["field6"], fields.FloatField)
        assert isinstance(result["schema"]["field7"], fields.CharField)
        assert isinstance(result["schema"]["field8"], fields.TextField)
        assert isinstance(result["schema"]["field9"], fields.JSONField)
        assert isinstance(result["schema"]["field10"], fields.EmailField)
        assert isinstance(result["schema"]["field11"], fields.URLField)
        assert isinstance(result["schema"]["field12"], fields.DateField)
        assert isinstance(result["schema"]["field13"], fields.DateTimeField)
        assert isinstance(result["schema"]["field14"], fields.DateTimeTZField)

    def test_load_from_yaml_with_additional_config(self, loader, temp_yaml_file):
        """Test that additional configuration items are preserved."""
        config = {
            "target_table": "orders",
            "schema": {"id": "IntegerField"},
            "hooks": {"pre_insert": "validate_order"},
            "batch_size": 1000,
            "parallel": True,
        }
        yaml.dump(config, temp_yaml_file)
        temp_yaml_file.flush()

        result = loader.load_from_yaml(temp_yaml_file.name)

        assert result["config"]["hooks"]["pre_insert"] == "validate_order"
        assert result["config"]["batch_size"] == 1000
        assert result["config"]["parallel"] is True

    def test_loader_has_cache_attribute(self, loader):
        """Test that SchemaLoader instance has a _cache attribute."""
        assert hasattr(loader, "_cache")
        assert isinstance(loader._cache, dict)

    def test_load_from_yaml_empty_schema(self, loader, temp_yaml_file):
        """Test loading configuration with empty schema."""
        config = {"target_table": "empty_table", "schema": {}}
        yaml.dump(config, temp_yaml_file)
        temp_yaml_file.flush()

        result = loader.load_from_yaml(temp_yaml_file.name)

        assert result["target_table"] == "empty_table"
        assert len(result["schema"]) == 0

    def test_load_from_yaml_mixed_field_definitions(self, loader, temp_yaml_file):
        """Test loading configuration with mixed string and dict field definitions."""
        config = {
            "target_table": "mixed_table",
            "schema": {
                "id": "IntegerField",
                "name": {"type": "CharField", "max_length": 100},
                "active": "BooleanField",
                "created_at": {"type": "DateTimeField", "nullable": False},
            },
        }
        yaml.dump(config, temp_yaml_file)
        temp_yaml_file.flush()

        result = loader.load_from_yaml(temp_yaml_file.name)

        assert len(result["schema"]) == 4
        assert isinstance(result["schema"]["id"], fields.IntegerField)
        assert isinstance(result["schema"]["name"], fields.CharField)
        assert isinstance(result["schema"]["active"], fields.BooleanField)
        assert isinstance(result["schema"]["created_at"], fields.DateTimeField)

    # Business Key Tests
    def test_load_from_yaml_with_single_business_key(self, loader, temp_yaml_file):
        """Test loading configuration with a single business key field."""
        config = {
            "target_table": "users",
            "schema": {
                "id": "IntegerField",
                "email": "EmailField",
                "name": "StringField",
            },
            "business_key": ["email"],
        }
        yaml.dump(config, temp_yaml_file)
        temp_yaml_file.flush()

        result = loader.load_from_yaml(temp_yaml_file.name)

        assert result["target_table"] == "users"
        assert "email" in result["schema"]
        assert result["config"]["business_key"] == ["email"]

    def test_load_from_yaml_with_multiple_business_keys(self, loader, temp_yaml_file):
        """Test loading configuration with multiple business key fields."""
        config = {
            "target_table": "transactions",
            "schema": {
                "id": "IntegerField",
                "account_id": "IntegerField",
                "transaction_date": "DateField",
                "amount": "FloatField",
            },
            "business_key": ["account_id", "transaction_date"],
        }
        yaml.dump(config, temp_yaml_file)
        temp_yaml_file.flush()

        result = loader.load_from_yaml(temp_yaml_file.name)

        assert result["target_table"] == "transactions"
        assert result["config"]["business_key"] == ["account_id", "transaction_date"]

    def test_load_from_yaml_with_empty_business_key_list(self, loader, temp_yaml_file):
        """Test loading configuration with empty business key list."""
        config = {
            "target_table": "users",
            "schema": {
                "id": "IntegerField",
                "name": "StringField",
            },
            "business_key": [],
        }
        yaml.dump(config, temp_yaml_file)
        temp_yaml_file.flush()

        result = loader.load_from_yaml(temp_yaml_file.name)

        assert result["target_table"] == "users"
        assert result["config"]["business_key"] == []

    def test_load_from_yaml_without_business_key(self, loader, temp_yaml_file):
        """Test loading configuration without business_key field (should default to empty list)."""
        config = {
            "target_table": "users",
            "schema": {
                "id": "IntegerField",
                "name": "StringField",
            },
        }
        yaml.dump(config, temp_yaml_file)
        temp_yaml_file.flush()

        result = loader.load_from_yaml(temp_yaml_file.name)

        assert result["target_table"] == "users"
        assert (
            "business_key" not in result["config"]
            or result["config"].get("business_key", []) == []
        )

    def test_load_from_yaml_business_key_not_list(self, loader, temp_yaml_file):
        """Test that ImproperlyConfigured is raised when business_key is not a list."""
        config = {
            "target_table": "users",
            "schema": {
                "id": "IntegerField",
                "email": "EmailField",
            },
            "business_key": "email",  # Should be a list, not a string
        }
        yaml.dump(config, temp_yaml_file)
        temp_yaml_file.flush()

        with pytest.raises(ImproperlyConfigured) as exc_info:
            loader.load_from_yaml(temp_yaml_file.name)
        assert "'business_key' must be a list" in str(exc_info.value)

    def test_load_from_yaml_business_key_field_not_in_schema(
        self, loader, temp_yaml_file
    ):
        """Test that ImproperlyConfigured is raised when business_key field is not in schema."""
        config = {
            "target_table": "users",
            "schema": {
                "id": "IntegerField",
                "name": "StringField",
            },
            "business_key": ["email"],  # 'email' not in schema
        }
        yaml.dump(config, temp_yaml_file)
        temp_yaml_file.flush()

        with pytest.raises(ImproperlyConfigured) as exc_info:
            loader.load_from_yaml(temp_yaml_file.name)
        assert "Business key field 'email' not found in schema" in str(exc_info.value)

    def test_load_from_yaml_one_business_key_field_not_in_schema(
        self, loader, temp_yaml_file
    ):
        """Test that ImproperlyConfigured is raised when one of multiple business_key fields is not in schema."""
        config = {
            "target_table": "transactions",
            "schema": {
                "id": "IntegerField",
                "account_id": "IntegerField",
                "amount": "FloatField",
            },
            "business_key": [
                "account_id",
                "transaction_date",
            ],  # 'transaction_date' not in schema
        }
        yaml.dump(config, temp_yaml_file)
        temp_yaml_file.flush()

        with pytest.raises(ImproperlyConfigured) as exc_info:
            loader.load_from_yaml(temp_yaml_file.name)
        assert "Business key field 'transaction_date' not found in schema" in str(
            exc_info.value
        )

    def test_load_from_yaml_business_key_with_dict_field_type(
        self, loader, temp_yaml_file
    ):
        """Test loading configuration with business_key using dict-style field definition."""
        config = {
            "target_table": "products",
            "schema": {
                "id": "IntegerField",
                "sku": {"type": "CharField", "max_length": 50, "unique": True},
                "name": "StringField",
            },
            "business_key": ["sku"],
        }
        yaml.dump(config, temp_yaml_file)
        temp_yaml_file.flush()

        result = loader.load_from_yaml(temp_yaml_file.name)

        assert result["target_table"] == "products"
        assert "sku" in result["schema"]
        assert isinstance(result["schema"]["sku"], fields.CharField)
        assert result["config"]["business_key"] == ["sku"]

    def test_load_from_yaml_business_key_all_fields(self, loader, temp_yaml_file):
        """Test loading configuration where all fields are business keys."""
        config = {
            "target_table": "composite_key_table",
            "schema": {
                "field1": "IntegerField",
                "field2": "StringField",
                "field3": "DateField",
            },
            "business_key": ["field1", "field2", "field3"],
        }
        yaml.dump(config, temp_yaml_file)
        temp_yaml_file.flush()

        result = loader.load_from_yaml(temp_yaml_file.name)

        assert result["target_table"] == "composite_key_table"
        assert len(result["config"]["business_key"]) == 3
        assert set(result["config"]["business_key"]) == {"field1", "field2", "field3"}

    def test_load_from_yaml_business_key_with_integer(self, loader, temp_yaml_file):
        """Test that ImproperlyConfigured is raised when business_key is an integer."""
        config = {
            "target_table": "users",
            "schema": {
                "id": "IntegerField",
                "name": "StringField",
            },
            "business_key": 123,  # Should be a list
        }
        yaml.dump(config, temp_yaml_file)
        temp_yaml_file.flush()

        with pytest.raises(ImproperlyConfigured) as exc_info:
            loader.load_from_yaml(temp_yaml_file.name)
        assert "'business_key' must be a list" in str(exc_info.value)

    def test_load_from_yaml_business_key_with_dict(self, loader, temp_yaml_file):
        """Test that ImproperlyConfigured is raised when business_key is a dict."""
        config = {
            "target_table": "users",
            "schema": {
                "id": "IntegerField",
                "email": "EmailField",
            },
            "business_key": {"email": True},  # Should be a list
        }
        yaml.dump(config, temp_yaml_file)
        temp_yaml_file.flush()

        with pytest.raises(ImproperlyConfigured) as exc_info:
            loader.load_from_yaml(temp_yaml_file.name)
        assert "'business_key' must be a list" in str(exc_info.value)


class TestCreateModel:
    """Test suite for the create_model method of SchemaLoader."""

    @pytest.fixture
    def loader(self):
        """Create a fresh SchemaLoader instance for each test."""
        return SchemaLoader()

    @pytest.fixture
    def basic_schema_definition(self):
        """Provide a basic schema definition for testing."""
        return {
            "target_table": "users",
            "schema": {
                "id": fields.IntegerField(),
                "username": fields.CharField(max_length=50),
                "email": fields.EmailField(),
            },
            "config": {
                "target_table": "users",
                "schema": {
                    "id": "IntegerField",
                    "username": {"type": "CharField", "max_length": 50},
                    "email": "EmailField",
                },
            },
        }

    @pytest.fixture
    def complex_schema_definition(self):
        """Provide a complex schema definition with various field types."""
        return {
            "target_table": "orders",
            "schema": {
                "id": fields.IntegerField(),
                "order_number": fields.CharField(max_length=20, unique=True),
                "customer_email": fields.EmailField(nullable=False),
                "total_amount": fields.FloatField(),
                "is_paid": fields.BooleanField(default=False),
                "order_date": fields.DateTimeField(),
                "metadata": fields.JSONField(),
                "notes": fields.TextField(),
            },
            "config": {},
        }

    def test_create_model_returns_type_class(self, loader, basic_schema_definition):
        """Test that create_model returns a type/class."""
        model_class = loader.create_model(basic_schema_definition)

        assert isinstance(model_class, type)
        assert issubclass(model_class, Model)

    def test_create_model_with_basic_schema(self, loader, basic_schema_definition):
        """Test creating a model with basic schema fields."""
        model_class = loader.create_model(basic_schema_definition)

        # Check class name
        assert model_class.__name__ == "Users"

        # Check that fields are present
        assert hasattr(model_class, "id")
        assert hasattr(model_class, "username")
        assert hasattr(model_class, "email")

        # Check that fields are of correct type
        assert isinstance(model_class.id, fields.IntegerField)
        assert isinstance(model_class.username, fields.CharField)
        assert isinstance(model_class.email, fields.EmailField)

    def test_create_model_with_complex_schema(self, loader, complex_schema_definition):
        """Test creating a model with complex schema containing many field types."""
        model_class = loader.create_model(complex_schema_definition)

        assert model_class.__name__ == "Orders"

        # Verify all fields are present
        assert hasattr(model_class, "id")
        assert hasattr(model_class, "order_number")
        assert hasattr(model_class, "customer_email")
        assert hasattr(model_class, "total_amount")
        assert hasattr(model_class, "is_paid")
        assert hasattr(model_class, "order_date")
        assert hasattr(model_class, "metadata")
        assert hasattr(model_class, "notes")

        # Verify field types
        assert isinstance(model_class.id, fields.IntegerField)
        assert isinstance(model_class.order_number, fields.CharField)
        assert isinstance(model_class.customer_email, fields.EmailField)
        assert isinstance(model_class.total_amount, fields.FloatField)
        assert isinstance(model_class.is_paid, fields.BooleanField)
        assert isinstance(model_class.order_date, fields.DateTimeField)
        assert isinstance(model_class.metadata, fields.JSONField)
        assert isinstance(model_class.notes, fields.TextField)

    def test_create_model_class_name_capitalization(self, loader):
        """Test that the model class name is properly capitalized."""
        schema_def = {
            "target_table": "user_profiles",
            "schema": {"id": fields.IntegerField()},
            "config": {},
        }

        model_class = loader.create_model(schema_def)
        assert model_class.__name__ == "User_profiles"

    def test_create_model_inherits_from_model(self, loader, basic_schema_definition):
        """Test that the created model properly inherits from AtomSQL Model."""
        model_class = loader.create_model(basic_schema_definition)

        # Check inheritance
        assert issubclass(model_class, Model)

        # Check that Model methods are available
        assert hasattr(model_class, "create")
        assert hasattr(model_class, "get_or_create")
        assert hasattr(model_class, "update_or_create")

    def test_create_model_instance_creation(self, loader, basic_schema_definition):
        """Test that instances can be created from the generated model."""
        model_class = loader.create_model(basic_schema_definition)

        # Create an instance
        instance = model_class(id=1, username="testuser", email="test@example.com")

        assert instance.id == 1
        assert instance.username == "testuser"
        assert instance.email == "test@example.com"

    def test_create_model_with_empty_schema(self, loader):
        """Test creating a model with an empty schema."""
        schema_def = {
            "target_table": "empty_table",
            "schema": {},
            "config": {},
        }

        model_class = loader.create_model(schema_def)

        assert model_class.__name__ == "Empty_table"
        assert issubclass(model_class, Model)
        # Model should still have the default 'id' field added by ModelMeta
        assert hasattr(model_class, "_fields")

    def test_create_model_module_attribute(self, loader, basic_schema_definition):
        """Test that the created model has the correct __module__ attribute."""
        model_class = loader.create_model(basic_schema_definition)

        assert model_class.__module__ == "atomingest.dynamic_models"

    def test_create_model_preserves_field_properties(self, loader):
        """Test that field properties (nullable, unique, etc.) are preserved."""
        schema_def = {
            "target_table": "test_table",
            "schema": {
                "id": fields.IntegerField(),
                "unique_code": fields.CharField(max_length=50, unique=True),
                "nullable_field": fields.TextField(nullable=True),
                "non_nullable": fields.EmailField(nullable=False),
            },
            "config": {},
        }

        model_class = loader.create_model(schema_def)

        # Check that field instances maintain their properties
        assert model_class.unique_code.unique is True
        assert model_class.nullable_field.nullable is True
        assert model_class.non_nullable.nullable is False

    def test_create_model_multiple_times_same_table(
        self, loader, basic_schema_definition
    ):
        """Test that create_model can be called multiple times with same table name."""
        model_class_1 = loader.create_model(basic_schema_definition)
        model_class_2 = loader.create_model(basic_schema_definition)

        # Should create separate classes
        assert model_class_1 is not model_class_2
        # But with the same name
        assert model_class_1.__name__ == model_class_2.__name__

    def test_create_model_different_tables(self, loader):
        """Test creating models for different tables."""
        schema_def_1 = {
            "target_table": "users",
            "schema": {"id": fields.IntegerField()},
            "config": {},
        }
        schema_def_2 = {
            "target_table": "orders",
            "schema": {"id": fields.IntegerField()},
            "config": {},
        }

        model_class_1 = loader.create_model(schema_def_1)
        model_class_2 = loader.create_model(schema_def_2)

        assert model_class_1.__name__ == "Users"
        assert model_class_2.__name__ == "Orders"
        assert model_class_1 is not model_class_2

    def test_create_model_with_all_field_types(self, loader):
        """Test creating a model with all supported field types."""
        schema_def = {
            "target_table": "comprehensive_table",
            "schema": {
                "field_base": fields.Field(),
                "field_bool": fields.BooleanField(),
                "field_int": fields.IntegerField(),
                "field_timestamp": fields.TimeStampField(),
                "field_string": fields.StringField(),
                "field_decimal": fields.FloatField(),
                "field_char": fields.CharField(max_length=100),
                "field_text": fields.TextField(),
                "field_json": fields.JSONField(),
                "field_email": fields.EmailField(),
                "field_url": fields.URLField(),
                "field_date": fields.DateField(),
                "field_datetime": fields.DateTimeField(),
                "field_datetimetz": fields.DateTimeTZField(),
                "field_uuid": fields.UUIDField(),
            },
            "config": {},
        }

        model_class = loader.create_model(schema_def)

        # Verify all fields exist and have correct types
        assert isinstance(model_class.field_base, fields.Field)
        assert isinstance(model_class.field_bool, fields.BooleanField)
        assert isinstance(model_class.field_int, fields.IntegerField)
        assert isinstance(model_class.field_timestamp, fields.TimeStampField)
        assert isinstance(model_class.field_string, fields.StringField)
        assert isinstance(model_class.field_decimal, fields.FloatField)
        assert isinstance(model_class.field_char, fields.CharField)
        assert isinstance(model_class.field_text, fields.TextField)
        assert isinstance(model_class.field_json, fields.JSONField)
        assert isinstance(model_class.field_email, fields.EmailField)
        assert isinstance(model_class.field_url, fields.URLField)
        assert isinstance(model_class.field_date, fields.DateField)
        assert isinstance(model_class.field_datetime, fields.DateTimeField)
        assert isinstance(model_class.field_datetimetz, fields.DateTimeTZField)
        assert isinstance(model_class.field_uuid, fields.UUIDField)

    def test_create_model_has_internal_fields_attribute(
        self, loader, basic_schema_definition
    ):
        """Test that created model has the _fields attribute set by ModelMeta."""
        model_class = loader.create_model(basic_schema_definition)

        assert hasattr(model_class, "_fields")
        assert isinstance(model_class._fields, dict)
        assert "id" in model_class._fields
        assert "username" in model_class._fields
        assert "email" in model_class._fields

    def test_create_model_has_table_name_attribute(
        self, loader, basic_schema_definition
    ):
        """Test that created model has the _table_name attribute set by ModelMeta."""
        model_class = loader.create_model(basic_schema_definition)

        assert hasattr(model_class, "_table_name")
        assert model_class._table_name == "users"  # lowercase by ModelMeta

    def test_create_model_with_special_characters_in_table_name(self, loader):
        """Test creating a model with special characters in table name."""
        schema_def = {
            "target_table": "user-profile_2024",
            "schema": {"id": fields.IntegerField()},
            "config": {},
        }

        model_class = loader.create_model(schema_def)
        assert model_class.__name__ == "User-profile_2024"

    def test_create_model_from_load_yaml_integration(self, loader):
        """Integration test: create model from YAML-loaded schema."""
        # Create temporary YAML file
        temp_file = tempfile.NamedTemporaryFile(mode="w", suffix=".yaml", delete=False)
        config = {
            "target_table": "products",
            "schema": {
                "id": "IntegerField",
                "name": {"type": "CharField", "max_length": 100},
                "price": {"type": "FloatField", "nullable": False},
                "in_stock": {"type": "BooleanField", "default": True},
            },
        }
        yaml.dump(config, temp_file)
        temp_file.flush()

        try:
            # Load from YAML
            schema_def = loader.load_from_yaml(temp_file.name)

            # Create model from loaded schema
            model_class = loader.create_model(schema_def)

            # Verify the model
            assert model_class.__name__ == "Products"
            assert hasattr(model_class, "id")
            assert hasattr(model_class, "name")
            assert hasattr(model_class, "price")
            assert hasattr(model_class, "in_stock")

            # Verify instance creation
            product = model_class(id=1, name="Test Product", price=19.99, in_stock=True)
            assert product.id == 1
            assert product.name == "Test Product"
            assert product.price == 19.99
            assert product.in_stock is True
        finally:
            temp_file.close()
            Path(temp_file.name).unlink(missing_ok=True)

    def test_create_model_instance_with_default_values(self, loader):
        """Test that instances use field defaults when values not provided."""
        schema_def = {
            "target_table": "settings",
            "schema": {
                "id": fields.IntegerField(),
                "enabled": fields.BooleanField(default=True),
                "count": fields.IntegerField(default=0),
            },
            "config": {},
        }

        model_class = loader.create_model(schema_def)
        instance = model_class(id=1)

        # Default values should be applied
        assert instance.id == 1
        assert instance.enabled is True
        assert instance.count == 0

    def test_create_model_field_access_via_class(self, loader, basic_schema_definition):
        """Test that fields can be accessed via the class itself."""
        model_class = loader.create_model(basic_schema_definition)

        # Fields should be accessible as class attributes
        username_field = model_class.username
        assert isinstance(username_field, fields.CharField)

        email_field = model_class.email
        assert isinstance(email_field, fields.EmailField)

    def test_create_model_with_only_target_table_and_schema(self, loader):
        """Test that create_model works with minimal required keys."""
        schema_def = {
            "target_table": "minimal",
            "schema": {
                "id": fields.IntegerField(),
                "name": fields.StringField(),
            },
        }

        model_class = loader.create_model(schema_def)

        assert model_class.__name__ == "Minimal"
        assert hasattr(model_class, "id")
        assert hasattr(model_class, "name")

    def test_create_model_preserves_field_max_length(self, loader):
        """Test that CharField max_length is preserved in created model."""
        schema_def = {
            "target_table": "test",
            "schema": {
                "short_field": fields.CharField(max_length=10),
                "long_field": fields.CharField(max_length=255),
            },
            "config": {},
        }

        model_class = loader.create_model(schema_def)

        assert model_class.short_field.max_length == 10
        assert model_class.long_field.max_length == 255

    def test_create_model_with_numeric_table_name_prefix(self, loader):
        """Test creating model when table name starts with number."""
        schema_def = {
            "target_table": "2024_orders",
            "schema": {"id": fields.IntegerField()},
            "config": {},
        }

        model_class = loader.create_model(schema_def)
        # Python class names can't start with numbers, but capitalize should work
        assert model_class.__name__ == "2024_orders"
