import email.message
import email.policy
import re
import textwrap

from ._text import FoldedCase


class RawPolicy(email.policy.EmailPolicy):
    def fold(self, name, value):
        folded = self.linesep.join(
            textwrap.indent(value, prefix=' ' * 8, predicate=lambda line: True)
            .lstrip()
            .splitlines()
        )
        return f'{name}: {folded}{self.linesep}'


class Message(email.message.Message):
    r"""
    Specialized Message subclass to handle metadata naturally.

    Reads values that may have newlines in them and converts the
    payload to the Description.

    >>> msg_text = textwrap.dedent('''
    ...     Name: Foo
    ...     Version: 3.0
    ...     License: blah
    ...             de-blah
    ...     <BLANKLINE>
    ...     First line of description.
    ...     Second line of description.
    ...     <BLANKLINE>
    ...     Fourth line!
    ...     ''').lstrip().replace('<BLANKLINE>', '')
    >>> msg = Message(email.message_from_string(msg_text))
    >>> msg['Description']
    'First line of description.\nSecond line of description.\n\nFourth line!\n'

    Message should render even if values contain newlines.

    >>> print(msg)
    Name: Foo
    Version: 3.0
    License: blah
            de-blah
    Description: First line of description.
            Second line of description.
    <BLANKLINE>
            Fourth line!
    <BLANKLINE>
    <BLANKLINE>
    """

    multiple_use_keys = set(
        map(
            FoldedCase,
            [
                'Classifier',
                'Obsoletes-Dist',
                'Platform',
                'Project-URL',
                'Provides-Dist',
                'Provides-Extra',
                'Requires-Dist',
                'Requires-External',
                'Supported-Platform',
                'Dynamic',
            ],
        )
    )
    """
    Keys that may be indicated multiple times per PEP 566.
    """

    def __new__(cls, orig: email.message.Message):
        res = super().__new__(cls)
        vars(res).update(vars(orig))
        return res

    def __init__(self, *args, **kwargs):
        self._headers = self._repair_headers()

    # suppress spurious error from mypy
    def __iter__(self):
        return super().__iter__()

    def __getitem__(self, item):
        """
        Override parent behavior to typical dict behavior.

        ``email.message.Message`` will emit None values for missing
        keys. Typical mappings, including this ``Message``, will raise
        a key error for missing keys.

        Ref python/importlib_metadata#371.
        """
        res = super().__getitem__(item)
        if res is None:
            raise KeyError(item)
        return res

    def _repair_headers(self):
        def redent(value):
            "Correct for RFC822 indentation"
            indent = ' ' * 8
            if not value or '\n' + indent not in value:
                return value
            return textwrap.dedent(indent + value)

        headers = [(key, redent(value)) for key, value in vars(self)['_headers']]
        if self._payload:
            headers.append(('Description', self.get_payload()))
            self.set_payload('')
        return headers

    def as_string(self):
        return super().as_string(policy=RawPolicy())

    @property
    def json(self):
        """
        Convert PackageMetadata to a JSON-compatible format
        per PEP 0566.
        """

        def transform(key):
            value = self.get_all(key) if key in self.multiple_use_keys else self[key]
            if key == 'Keywords':
                value = re.split(r'\s+', value)
            tk = key.lower().replace('-', '_')
            return tk, value

        return dict(map(transform, map(FoldedCase, self)))
