import re
from abc import ABC, abstractmethod
from typing import Any, Dict

from django_scaffolding_tools.django.utils import get_decimal_info, get_max_length


class ModelFieldHandler(ABC):
    @abstractmethod
    def set_next(self, handler: "ModelFieldHandler") -> "ModelFieldHandler":
        pass

    @abstractmethod
    def handle(self, field_data: Dict[str, Any]) -> Dict[str, Any] | None:
        pass


class AbstractModelFieldHandler(ModelFieldHandler):
    _next_handler: ModelFieldHandler = None
    field = None
    _next_handlers = list()
    _current_index = 0

    def set_next(self, handler: "ModelFieldHandler") -> "ModelFieldHandler":
        self._next_handler = handler
        return handler

    def handle(self, field_data: Dict[str, Any]) -> Dict[str, Any] | None:
        if self._next_handler is not None:
            return self._next_handler.handle(field_data)
        return None

    def __iter__(self):
        self._next_handlers = list()
        self._current_index = 0
        my_next = self._next_handler
        self._next_handlers.append(my_next)
        while True:
            my_next = my_next._next_handler
            if my_next is None:
                break
            self._next_handlers.append(my_next)
        for handler in self._next_handlers:
            yield handler

    def __len__(self):
        self.__iter__()
        return len(self._next_handlers)


class DateTimeFieldHandler(AbstractModelFieldHandler):
    field = "DateTimeField"

    def handle(self, field_data: Dict[str, Any]) -> Dict[str, Any] | None:
        if field_data["data_type"] == self.field:
            field_data["factory_field"] = (
                'LazyAttribute(lambda x: faker.date_time_between(start_date="-1y", '
                'end_date="now", tzinfo=timezone(settings.TIME_ZONE)))'
            )
            return field_data
        return super().handle(field_data)


class DateFieldHandler(AbstractModelFieldHandler):
    field = "DateField"

    def handle(self, field_data: Dict[str, Any]) -> Dict[str, Any] | None:
        if field_data["data_type"] == self.field:
            field_data["factory_field"] = (
                'LazyAttribute(lambda x: faker.date_time_between(start_date="-1y", '
                'end_date="now", tzinfo=timezone(settings.TIME_ZONE)).date())'
            )
            return field_data
        return super().handle(field_data)


class ForeignKeyFieldHandler(AbstractModelFieldHandler):
    field = "ForeignKey"

    def handle(self, field_data: Dict[str, Any]) -> Dict[str, Any] | None:
        if field_data["data_type"] == self.field:
            class_name = field_data["arguments"][0]["value"]  # FIXME very flaky
            value = f"SubFactory({class_name}Factory)"
            field_data["factory_field"] = value
            return field_data
        return super().handle(field_data)


class IntegerFieldHandler(AbstractModelFieldHandler):
    field = "IntegerField"

    def __init__(self):
        regexp_str = r".*(datetime|timestamp|time).*"
        self.regexp = re.compile(regexp_str)

    def handle(self, field_data: Dict[str, Any]) -> Dict[str, Any] | None:
        if field_data["data_type"] == self.field:
            match = self.regexp.match(field_data["name"])
            if match is None:
                value = "LazyAttribute(lambda o: randint(1, 100))"
            else:
                value = (
                    'LazyAttribute(lambda x: faker.date_time_between(start_date="-1y", '
                    'end_date="now", tzinfo=timezone(settings.TIME_ZONE)).timestamp())'
                )
            field_data["factory_field"] = value
            return field_data
        return super().handle(field_data)


class CharFieldHandler(AbstractModelFieldHandler):
    field = "CharField"

    def __init__(self):
        regexp_str = r".*(id|key|number).*"
        self.regexp = re.compile(regexp_str)

    def handle(self, field_data: Dict[str, Any]) -> Dict[str, Any] | None:
        if field_data["data_type"] == self.field:
            max_length = get_max_length(field_data)
            match = self.regexp.match(field_data["name"])
            if match is None:
                value = f"LazyAttribute(lambda x: FuzzyText(length={max_length}, chars=string.ascii_uppercase).fuzz())"
            else:
                value = f"LazyAttribute(lambda x: FuzzyText(length={max_length}, chars=string.digits).fuzz())"
            field_data["factory_field"] = value
            return field_data
        return super().handle(field_data)


class DecimalFieldHandler(AbstractModelFieldHandler):
    field = "DecimalField"

    def __init__(self, min_value: str = "25.00", max_value: str = "500.00"):
        self.min_value = min_value
        self.max_value = max_value

    def handle(self, field_data: Dict[str, Any]) -> Dict[str, Any] | None:
        if field_data["data_type"] == self.field:
            max_digits, decimal_places = get_decimal_info(field_data)
            value = (
                f"LazyAttribute(lambda x: faker.pydecimal(left_digits={max_digits - decimal_places}, "
                f"right_digits={decimal_places}, "
                f"positive=True, min_value={self.min_value}, max_value={self.max_value}))"
            )
            field_data["factory_field"] = value
            return field_data
        return super().handle(field_data)


class BooleanFieldHandler(AbstractModelFieldHandler):
    field = "BooleanField"

    def handle(self, field_data: Dict[str, Any]) -> Dict[str, Any] | None:
        if field_data["data_type"] == self.field:
            value = "Iterator([True, False])"
            field_data["factory_field"] = value
            return field_data
        return super().handle(field_data)


class DateTimeCharFieldHandler(AbstractModelFieldHandler):
    field = "CharField"

    def __init__(self):
        regexp_str = r".*(datetime|date|time).*"
        self.regexp = re.compile(regexp_str)
        self.date_format = "%Y-%m-%dT%H:%M:%S%z"

    def handle(self, field_data: Dict[str, Any]) -> Dict[str, Any] | None:
        if field_data["data_type"] == self.field:
            match = self.regexp.match(field_data["name"])
            if match is not None:
                value = (
                    f'LazyAttribute(lambda x: faker.date_time_between(start_date="-1y", '
                    f'end_date="now", tzinfo=timezone(settings.TIME_ZONE)).strftime({self.date_format}))'
                )
            else:
                return super().handle(field_data)
            field_data["factory_field"] = value
            return field_data
        return super().handle(field_data)


class EmailFieldHandler(AbstractModelFieldHandler):
    field = "EmailField"

    def handle(self, field_data: Dict[str, Any]) -> Dict[str, Any] | None:
        if field_data["data_type"] == self.field:
            field_data["factory_field"] = "LazyAttribute(lambda x: faker.ascii_free_email())"
            return field_data
        return super().handle(field_data)


class JSONFieldHandler(AbstractModelFieldHandler):
    field = "JSONField"

    def handle(self, field_data: Dict[str, Any]) -> Dict[str, Any] | None:
        if field_data["data_type"] == self.field:
            field_data["factory_field"] = "LazyAttribute(lambda x: faker.pydict(5, value_types=[str, int, float]))"
            return field_data
        return super().handle(field_data)
