from ..advisors import snippet_advisor
from .. import conf
from ..utils import get_nice_str_list, int2nice, layout_comment as layout

UNSPECIFIC_EXCEPTION = 'Exception'

def _get_exception_block_comment(exception_block):
    handlers = get_nice_str_list(exception_block, quoter='`')
    comment = f"The following exception handlers were detected: {handlers}"
    return comment

def get_exception_blocks(blocks_dets):
    """
    There can be multiple try-except statements in a snippet so we have to
    handle each of them.
    """
    exception_blocks = []
    for block_dets in blocks_dets:
        block_exception_types = []
        except_handler_els = block_dets.element.xpath(
            'descendant-or-self::handlers/ExceptHandler')
        for except_handler_el in except_handler_els:
            exception_type_els = except_handler_el.xpath(
                'type/Name | type/Tuple/elts/Name')
            exception_handler_types = [
                exception_type_el.get('id')
                for exception_type_el in exception_type_els]
            block_exception_types.extend(exception_handler_types)
        if block_exception_types:
            exception_blocks.append(block_exception_types)
    return exception_blocks

@snippet_advisor()
def exception_overview(blocks_dets):
    """
    Provide overview of exception handling.
    """
    exception_blocks = get_exception_blocks(blocks_dets)
    if not exception_blocks:
        return None
    brief_msg = '### Exception handling'
    for n, exception_block in enumerate(exception_blocks, 1):
        counter = '' if len(exception_blocks) == 1 else f" {int2nice(n)}"
        brief_msg += layout(f"""\

            #### `try`-`except` block{counter}

            """)
        brief_msg += _get_exception_block_comment(exception_block)
    message = {
        conf.BRIEF: layout(brief_msg),
    }
    return message

@snippet_advisor(warning=True)
def unspecific_exception(blocks_dets):
    """
    Look for unspecific exceptions.
    """
    exception_blocks = get_exception_blocks(blocks_dets)
    if not exception_blocks:
        return None
    brief_msg = ''
    unspecific_block_ns = []
    has_title = False
    has_unspecific = False
    for n, exception_block in enumerate(exception_blocks, 1):
        only_unspecific = (len(exception_block) == 1
            and exception_block[0] == UNSPECIFIC_EXCEPTION)
        if not only_unspecific:
            continue
        has_unspecific = True
        unspecific_block_ns.append(n)
        if not has_title:
            brief_msg += layout("""\

                #### Un-specific `Exception` only in `try`-`except` block(s)

                """)
        has_title = True
    if not has_unspecific:
        return None
    n_unspecific = len(unspecific_block_ns)
    if n_unspecific == 1:
        block_n_specific_text = ' has'
    else:
        unspecific_nice_block_ns = [
            int2nice(unspecific_block_n)
            for unspecific_block_n in unspecific_block_ns]
        blocks_ns = get_nice_str_list(unspecific_nice_block_ns, quoter='')
        block_n_specific_text = f"s {blocks_ns} have"
    brief_msg += layout(f"""\

        `try`-`except` block{block_n_specific_text} an un-specific Exception
        only.

        Using the un-specific exception type `Exception` is often completely
        appropriate. But if you are looking for specific exceptions you
        should handle those separately.

        For example:
        """)
    message = {
        conf.BRIEF: layout(brief_msg),
        conf.MAIN: (
            layout(brief_msg)
            +
            layout("""\

                try:
                    spec_dicts[idx][spec_type]
                except IndexError:
                    print(f"Unable to access requested spec_dict (idx {idx})")
                except KeyError:
                    print(f"Unable to access '{spec_type}' for "
                        f"requested spec_dict (idx {idx})")
                except Exception as e:
                    print(f"Unexpected exception - details: {e}")
                """, is_code=True)
        ),
    }
    return message
