import pprint
from typing import Optional, Union

from bs4 import BeautifulSoup, Tag
from bs4.element import NavigableString, PreformattedString

from airium import Tag as AiriumTag


def from_html_to_airium(html_str: str, include_module_head: bool = True) -> str:
    root_ = BeautifulSoup(html_str, 'html.parser')
    ret = translate_el(root_, 'a') or ""
    if include_module_head:
        ret = f"""\
#!/usr/bin/env python
# File generated by reverse AIRIUM translator.
# Any change will be overridden on next run.
# flake8: noqa E501 (line too long)

from airium import Airium

a = Airium()

{ret}
"""
    return ret


ATTRIBUTE_INVERSION = {v: k for k, v in sorted(AiriumTag.ATTRIBUTE_NAME_SUBSTITUTES.items())}


def translate_arguments(root: Tag):
    clean_args = []
    other_args = {}
    for k, v in sorted(root.attrs.items()):

        k = ATTRIBUTE_INVERSION.get(k, k)
        if isinstance(v, list):
            v = ' '.join(v)
        if not k.isidentifier():
            other_args[k] = v
        else:
            clean_args.append(f'{k}={v!r}')

    if other_args:
        clean_args.append(f"**{pprint.pformat(other_args)}")

    ret = ', '.join(clean_args)

    return ret


def translate_el(node: Union[Tag, PreformattedString, NavigableString, str], doc_obj: str) -> Optional[str]:
    nl = '\n'
    if isinstance(node, BeautifulSoup):
        sub_elements = filter(bool, [translate_el(ch, doc_obj) or '' for ch in node.contents])
        return '\n'.join(sub_elements)

    if isinstance(node, PreformattedString):
        return f'{doc_obj}({node.output_ready().strip()!r})'

    if isinstance(node, NavigableString):
        return f'{doc_obj}({node.strip()!r})' if node.strip() else None

    elif not isinstance(node, Tag):
        raise TypeError(f"Unsupported element type: {type(node).__name__}: {str(node)[:20].replace(nl, ' ')}")

    sub_elements = filter(None, [translate_el(ch, doc_obj) or '' for ch in node.contents])

    children = '\n'.join(map(increase_indent, sub_elements))
    args = translate_arguments(node)

    attribute_getter: str = node.name
    if not attribute_getter.isidentifier():
        attribute_getter = f'get_tag_({attribute_getter!r})'

    children_types = {type(c).__name__ for c in node.contents if str(c).strip()}
    has_text = any(str(c).strip() for c in node.contents)

    if children_types == {'NavigableString'} and has_text:
        text = ' '.join(p for c in node.contents for p in str(c).split())

        if len(attribute_getter) + len(args) + len(text) < 100:
            if args:
                args += ', '
            args += f"_t={text!r}"

            return f"{doc_obj}.{attribute_getter}({args})"

    elif not children:
        return f"{doc_obj}.{attribute_getter}({args})"

    return f"with {doc_obj}.{attribute_getter}({args}):\n{children}"


def increase_indent(multi_line_text: str) -> str:
    indent_str = ' ' * 4
    return ''.join(
        f"{indent_str}{line}" if line.strip() else "\n" for line in str(multi_line_text).splitlines(keepends=True))
