from django.forms import Form
import factory
import wagtail_factories

from opentech.apply.models import FundPage
from opentech.apply.workflow import Action, Phase, Stage, Workflow


class ListSubFactory(factory.SubFactory):
    def __init__(self, *args, count=0, **kwargs):
        self.count = count
        super().__init__(*args, **kwargs)

    def evaluate(self, *args, **kwargs):
        if isinstance(self.count, factory.declarations.BaseDeclaration):
            self.evaluated_count = self.count.evaluate(*args, **kwargs)
        else:
            self.evaluated_count = self.count

        return super().evaluate(*args, **kwargs)

    def generate(self, step, params):
        subfactory = self.get_factory()
        force_sequence = step.sequence if self.FORCE_SEQUENCE else None
        return [
            step.recurse(subfactory, params, force_sequence=force_sequence)
            for _ in range(self.evaluated_count)
        ]


class ActionFactory(factory.Factory):
    class Meta:
        model = Action

    name = factory.Faker('word')


class PhaseFactory(factory.Factory):
    class Meta:
        model = Phase

    class Params:
        num_actions = factory.Faker('random_int', min=1, max=5)

    name = factory.Faker('word')
    actions = ListSubFactory(ActionFactory, count=factory.SelfAttribute('num_actions'))
    stage = factory.PostGeneration(
        lambda obj, create, extracted, **kwargs: StageFactory.build(phases=[obj])
    )

    @classmethod
    def _create(cls, model_class, *args, **kwargs):
        actions = kwargs.pop('actions')
        new_class = type(model_class.__name__, (model_class,), {'actions': actions})
        return new_class(*args, **kwargs)

    @classmethod
    def _build(cls, model_class, *args, **kwargs):
        # defer to create because parent uses build
        return cls._create(model_class, *args, **kwargs)


class StageFactory(factory.Factory):
    class Meta:
        model = Stage
        inline_args = ('form',)

    class Params:
        num_phases = factory.Faker('random_int', min=1, max=3)

    name = factory.Faker('word')
    form = factory.LazyFunction(Form)
    phases = ListSubFactory(PhaseFactory, count=factory.SelfAttribute('num_phases'))

    @classmethod
    def _create(cls, model_class, *args, **kwargs):
        # Returns a new class
        phases = kwargs.pop('phases')
        name = kwargs.pop('name')
        return type(model_class.__name__, (model_class,), {'phases': phases, 'name': name})

    @classmethod
    def _build(cls, model_class, *args, **kwargs):
        # returns an instance of the stage class
        phases = kwargs.pop('phases')
        name = kwargs.pop('name')
        new_class = type(model_class.__name__, (model_class,), {'phases': phases, 'name': name})
        return new_class(*args, **kwargs)


class WorkflowFactory(factory.Factory):
    class Meta:
        model = Workflow
        rename = {'stages': 'stage_classes'}

    class Params:
        num_stages = factory.Faker('random_int', min=1, max=3)

    name = factory.Faker('word')
    stages = ListSubFactory(StageFactory, count=factory.SelfAttribute('num_stages'))

    @factory.LazyAttribute
    def forms(self):
        return [Form() for _ in range(self.num_stages)]

    @classmethod
    def _create(cls, model_class, *args, **kwargs):
        name = kwargs.pop('name')
        stages = kwargs.pop('stage_classes')
        new_class = type(model_class.__name__, (model_class,), {'name': name, 'stage_classes': stages})
        return new_class(*args, **kwargs)


class FundPageFactory(wagtail_factories.PageFactory):
    class Meta:
        model = FundPage