from typing import Any

from pythonpoet.types.decorator import Decoratorable
from pythonpoet.types.import_ import ImportBuilder, Import
from pythonpoet.types import DeserializableType, Argumentable, Builder


def _generate_arguments(arguments: list[list[str, Any]]) -> str:
    arguments_source_code = ""
    for argument in arguments:
        arguments_source_code += f"{argument[0]}: {argument[1]}"
    return arguments_source_code


def _generate_method_header(
    method_name: str, arguments: list[list[str, Any]], return_type: str
) -> str:
    if return_type is not None:
        return f"def {method_name}({_generate_arguments(arguments)}) -> {return_type}:\n\t\t"
    else:
        return f"def {method_name}({_generate_arguments(arguments)}):\n\t\t"


class Method(DeserializableType):
    """
    Representation of a class method.

    .. warning::

        You shouldn't initialize this class via the `__init__`. Use :class:`MethodBuilder` instead.

    Attributes
    ----------
    name : str
        Method's name.
    imports: list[:class:`Import`]
        Method's required imports.
    arguments: list[list[str, :class:`Any`]]
        Method's arguments.
    return_type : str or None
        Method's return type.
    source : str
        Method's source code.
    """

    def __init__(
        self,
        name: str,
        arguments: list[list[str, Any]],
        imports: list[Import],
        return_type: str | None,
        source: str,
    ) -> None:
        self.name = name
        self.imports = imports
        self.arguments = arguments
        self.return_type = return_type
        self.source = source

    def get_imports(self) -> str:
        imports_source_code = ""
        for import_ in self.imports:
            imports_source_code += import_.to_string()
        return imports_source_code

    def deserialize(self) -> str:
        return (
            _generate_method_header(self.name, self.arguments, self.return_type)
            + self.source
        )

    def __repr__(self) -> str:
        return (
            f"<Method name={self.name}, arguments={self.arguments}, "
            f"return_type={self.return_type}, source={self.source}>"
        )

    def __str__(self) -> str:
        return self.__repr__()


class MethodBuilder(Builder, Argumentable, Decoratorable):
    """
    Builder for the class :class:`Method`.

    Attributes
    ----------
    name : str
        Method's name.
    imports: list[:class:`Import`]
        Method's required imports.
    arguments: list[list[str, :class:`Any`]]
        Method's arguments.
    return_type : str or None
        Method's return type.
    source : str
        Method's source code.
    """

    def __init__(self) -> None:
        super().__init__()
        self.name: str | None = None
        self.imports: list[Import] = []
        self.return_type: str | None = None
        self.source: str = "pass"

    def set_name(self, name: str) -> "MethodBuilder":
        """
        Sets a new method's name.

        Parameters
        ----------
        name : str
            New method's name.

        Returns
        -------
        :class:`MethodBuilder`
            Updated builder's instance.
        """

        self.name = name
        return self

    def add_import(self, import_: Import) -> "MethodBuilder":
        """
        Adds a new method's import.

        Parameters
        ----------
        import_ : :class:`Import`
            Required method's import.

        Returns
        -------
        :class:`MethodBuilder`
            Updated builder's instance.
        """
        self.imports.append(import_)
        return self

    def set_return_type(
        self, return_type: type, import_: ImportBuilder = None
    ) -> "MethodBuilder":
        """
        Sets a new return type.

        Parameters
        ----------
        return_type : :class:`type`
            New method's return type.
        import_ : :class:`ImportBuilder`, optional, default: None
            Return's type import.

        Returns
        -------
        :class:`MethodBuilder`
            Updated builder's instance.
        """
        self.return_type = return_type.__name__
        if import_ is not None:
            self.imports.append(import_.build())
        return self

    # TODO: use an abstraction instead of using str.
    def set_source_code(self, source: str) -> "MethodBuilder":
        """
        Sets a new method's source code.

        Parameters
        ----------
        source : str
            New method's source code.

        Returns
        -------
        :class:`MethodBuilder`
            Updated builder's instance.
        """
        self.source = source
        return self

    def build(self):
        if self.name is None:
            raise ValueError("Method's name cannot be None.")

        return Method(
            self.name, self.arguments, self.imports, self.return_type, self.source
        )
