from functools import partial
from typing import List, Optional

import attr


__all__ = 'NewCheckRequest', 'UpdateCheckRequest', 'to_gh_query'


str_attrib = partial(attr.ib, converter=str)

optional_attrib = partial(
    attr.ib,
    default=None,
)

optional_str_attrib = partial(
    optional_attrib,
    validator=attr.validators.optional(lambda *_: str(_[-1])),
)

optional_list_attrib = partial(
    attr.ib,
    default=[],
    validator=attr.validators.optional(lambda *_: list(_[-1])),
)

def optional_converter(kwargs_dict, convert_to_cls):
    if kwargs_dict is not None and not isinstance(kwargs_dict, convert_to_cls):
        return convert_to_cls(**kwargs_dict)
    return kwargs_dict

def optional_list_converter(args_list, convert_to_cls):
    if args_list is not None and isinstance(args_list, list):
        return [optional_converter(kwargs_dict, convert_to_cls) for kwargs_dict in args_list]
    return args_list


@attr.dataclass
class CheckAnnotation:
    filename: str = str_attrib()
    blob_href: str = str_attrib()
    start_line: str = str_attrib()
    end_line: str = str_attrib()
    warning_level: str = str_attrib()
    message: str = str_attrib()
    title: Optional[str] = optional_str_attrib()
    raw_details: Optional[str] = optional_str_attrib()


@attr.dataclass
class CheckImage:
    alt: str = str_attrib()
    image_url: str = str_attrib()
    caption: Optional[str] = optional_str_attrib()


@attr.dataclass
class CheckActions:
    label: str = str_attrib()
    description: str = str_attrib()
    identifier: str = str_attrib()

    @label.validator
    def label_up_to_20(self, attribute, value):
        if len(value) > 20:
            raise ValueError(f'`{attribute.name}` must not exceed 20 characters.')

    @description.validator
    def description_up_to_40(self, attribute, value):
        if len(value) > 40:
            raise ValueError(f'`{attribute.name}` must not exceed 40 characters.')

    @identifier.validator
    def identifier_up_to_20(self, attribute, value):
        if len(value) > 20:
            raise ValueError(f'`{attribute.name}` must not exceed 20 characters.')


@attr.dataclass
class CheckOutput:
    title: str = str_attrib()
    summary: str = str_attrib()
    text: Optional[str] = optional_str_attrib()  # it looks like it's not optional; it can be empty string, but not missing
    annotations: List[CheckAnnotation] = optional_list_attrib(converter=partial(optional_list_converter, convert_to_cls=CheckAnnotation))
    images: List[CheckImage] = optional_list_attrib(converter=partial(optional_list_converter, convert_to_cls=CheckImage))


@attr.dataclass
class BaseCheckRequestMixin:
    name: str = str_attrib()
    details_url: Optional[str] = optional_str_attrib()
    external_id: Optional[str] = optional_str_attrib()
    status: Optional[str] = attr.ib(
        default=None,
        validator=attr.validators.optional(
            attr.validators.in_(
                (
                    'queued',
                    'in_progress',
                    'completed',
                )
            )
        )
    )
    started_at: Optional[str] = optional_str_attrib()  # '2018-05-27T14:30:33Z', datetime.isoformat()
    conclusion: Optional[str] = attr.ib(
        # [required] if 'status' is set to 'completed', should be missing if it's unset
        default=None,
        validator=attr.validators.optional(
            attr.validators.in_(
                (
                    'success',
                    'failure',
                    'neutral',
                    'cancelled',
                    'timed_out',
                    'action_required',
                )
            )
        ),
    )
    completed_at: Optional[str] = optional_str_attrib()  # [required] if 'conclusion' is set  # '2018-05-27T14:30:33Z',
    output: Optional[CheckOutput] = optional_attrib(converter=partial(optional_converter, convert_to_cls=CheckOutput))
    actions: List[CheckActions] = optional_list_attrib(converter=partial(optional_list_converter, convert_to_cls=CheckActions))

    @conclusion.validator
    def depends_on_status(self, attribute, value):
        if self.status == 'completed' and not value:
            raise ValueError(f'`{attribute.name}` must be provided if status is completed')

    @completed_at.validator
    def depends_on_completed_at(self, attribute, value):
        if self.conclusion and not value:
            raise ValueError(f'`{attribute.name}` must be provided if conclusion is present')

    @actions.validator
    def actions_up_to_3(self, attribute, value):
        if len(value) > 3:
            raise ValueError(f'`{attribute.name}` must not exceed 3 items.')


@attr.dataclass
class NewCheckRequestMixin:
    head_branch: str = str_attrib()
    head_sha: str = str_attrib()


@attr.dataclass
class NewCheckRequest(NewCheckRequestMixin, BaseCheckRequestMixin):
    pass


@attr.dataclass
class UpdateCheckRequest(BaseCheckRequestMixin):
    pass


def conditional_to_gh_query(req):
    if hasattr(req, '__attrs_attrs__'):
        return to_gh_query(req)
    if isinstance(req, list):
        return list(map(conditional_to_gh_query, req))
    return req


def to_gh_query(req):
    return {
        k: conditional_to_gh_query(v)  # recursive if dataclass or list
        for k, v in attr.asdict(req).items()
        if v is not None
    }
