from __future__ import annotations

from typing import TYPE_CHECKING, Any

import monkay

from edgy.testing.factory.base import ModelFactory
from edgy.utils.compat import is_class_and_subclass

if TYPE_CHECKING:
    from edgy.testing.factory import FactoryField


def SubFactory(factory_class: Any, **kwargs: Any) -> FactoryField:
    """
    Creates a `FactoryField` that builds a single instance from another `ModelFactory`.

    This function allows you to compose factories, where one factory can
    specify that a particular field should be populated by an instance
    generated by a different `ModelFactory`. This is particularly useful
    for handling relationships (e.g., `ForeignKey`, `OneToOneField`) between models.

    Parameters:
        factory_class (Any): The `ModelFactory` class to use for generating
                             the sub-instance. This can be a direct class reference
                             or a string in dotted path notation (e.g., "my_app.factories.UserFactory").
        **kwargs (Any): Additional keyword arguments to pass to the `__init__`
                        method of the `factory_class` when it is instantiated.
                        These arguments can override defaults or specify specific
                        values for the sub-factory's generated instance.

    Returns:
        FactoryField: A `FactoryField` configured to generate a single instance
                      using the specified `factory_class`.

    Raises:
        ValueError: If `factory_class` is not a subclass of `ModelFactory`
                    or a valid string representation thereof.
    """
    # If factory_class is a string, attempt to load the class dynamically.
    if isinstance(factory_class, str):
        factory_class = monkay.load(factory_class)

    # Validate that the loaded/provided factory_class is a subclass of ModelFactory.
    if not is_class_and_subclass(factory_class, ModelFactory):
        raise ValueError(
            "factory_class must be a subclass of ModelFactory or a string '.' "
            f"dotted represented of the factory, got {type(factory_class)} instead."
        )
    # Instantiate the sub-factory with provided kwargs and convert it to a FactoryField.
    return factory_class(**kwargs).to_factory_field()


def ListSubFactory(factory_class: Any, min: int = 0, max: int = 10, **kwargs: Any) -> FactoryField:
    """
    Creates a `FactoryField` that builds a list of instances from another `ModelFactory`.

    Similar to `SubFactory`, but designed for generating multiple related instances,
    making it suitable for "many" relationships (e.g., `ManyToManyField`, `RefForeignKey`).
    The number of instances generated will be a random integer between `min` and `max` (inclusive).

    Parameters:
        factory_class (Any): The `ModelFactory` class to use for generating
                             the sub-instances. This can be a direct class reference
                             or a string in dotted path notation.
        min (int, optional): The minimum number of instances to generate in the list.
                             Defaults to 0.
        max (int, optional): The maximum number of instances to generate in the list.
                             Defaults to 10.
        **kwargs (Any): Additional keyword arguments to pass to the `__init__`
                        method of the `factory_class` when it is instantiated.
                        These arguments can override defaults or specify specific
                        values for the sub-factory's generated instances.

    Returns:
        FactoryField: A `FactoryField` configured to generate a list of instances
                      using the specified `factory_class`.

    Raises:
        ValueError: If `factory_class` is not a subclass of `ModelFactory`
                    or a valid string representation thereof.
    """
    # If factory_class is a string, attempt to load the class dynamically.
    if isinstance(factory_class, str):
        factory_class = monkay.load(factory_class)

    # Validate that the loaded/provided factory_class is a subclass of ModelFactory.
    if not is_class_and_subclass(factory_class, ModelFactory):
        raise ValueError(
            "factory_class must be a subclass of ModelFactory or a string '.' "
            f"dotted represented of the factory, got {type(factory_class)} instead."
        )
    # Instantiate the sub-factory with provided kwargs and convert it to a list FactoryField.
    return factory_class(**kwargs).to_list_factory_field(min=min, max=max)
