"""Collect summary of CSP reports."""
from __future__ import unicode_literals

from operator import attrgetter

from django.template import Context, Engine
from six.moves.urllib.parse import urlsplit, urlunsplit

from cspreports.models import CSPReport

DEFAULT_TOP = 10


def get_root_uri(uri):
    """Return root URI - strip query and fragment."""
    chunks = urlsplit(uri)
    return urlunsplit((chunks.scheme, chunks.netloc, chunks.path, '', ''))


class ViolationInfo(object):
    """Container for violation details.

    @ivar root_uri: A violation root URI.
    @ivar count: A number of violations related to the root URI.
    @ivar examples: List of violation examples.
    @ivar top: Maximal number of examples.
    """

    def __init__(self, root_uri, top=DEFAULT_TOP):
        self.root_uri = root_uri
        self.count = 0
        self.examples = []
        self.top = top

    def append(self, report):
        """Append a new CSP report."""
        assert report not in self.examples
        self.count += 1
        if len(self.examples) < self.top:
            self.examples.append(report)


SUMMARY_TEMPLATE = (
    'CSP report summary\n'
    '==================\n'
    'Start: {{ since }}\n'
    'End: {{ to }}\n'
    'Total number of reports: {{ total_count }}\n'
    'Total number of valid reports: {{ valid_count }}\n'
    'Total number of invalid reports: {{ invalid_count }}\n'
    '{% if sources %}'
        '\n'
        'Violation sources\n'
        '=================\n'
        '{% for info in sources %}'
            '\n'
            '{{ info.root_uri }}: {{ info.count }}\n'
            '----------------------------------------\n'
            '{% for example in info.examples %}'
                '{{ example|safe }}\n'
            '{% endfor %}'
        '{% endfor %}'
    '{% endif %}'
    '{% if blocks %}'
        '\n'
        'Blocked URIs\n'
        '============\n'
        '{% for info in blocks %}'
            '\n'
            '{{ info.root_uri }}: {{ info.count }}\n'
            '----------------------------------------\n'
            '{% for example in info.examples %}'
                '{{ example|safe }}\n'
            '{% endfor %}'
        '{% endfor %}'
    '{% endif %}'
    '{% if invalid_reports %}'
        '\n'
        'Invalid reports\n'
        '===============\n'
        '{% for example in invalid_reports %}'
            '{{ example|safe }}\n'
        '{% endfor %}'
    '{% endif %}'
) # NOQA


class CspReportSummary(object):
    """CSP report summary.

    @ivar since: Date and time of the summary start
    @ivar to: Date and time of the summary end
    @ivar top: Size of each section

    @ivar total_count: Total number of CSP reports
    @ivar valid_count: Total number of valid reports
    @ivar invalid_count: Total number of invalid reports

    @ivar sources: List of top sources of violations ordered by descending count
    @type sources: List[ViolationInfo]
    @ivar blocks: List of top blocked URIs ordered by descending count
    @type blocks: List[ViolationInfo]
    @ivar invalid_reports: Examples of invalid CSP reports
    """

    def __init__(self, since, to, top=DEFAULT_TOP):
        self.since = since
        self.to = to
        self.top = top

        self.total_count = 0
        self.valid_count = 0
        self.sources = []
        self.blocks = []
        self.invalid_count = 0
        self.invalid_reports = ()

    def render(self):
        """Render the summary."""
        engine = Engine()
        return engine.from_string(SUMMARY_TEMPLATE).render(Context(self.__dict__))


def collect(since, to, top=DEFAULT_TOP):
    """Collect the CSP report.

    @returntype: CspReportSummary
    """
    summary = CspReportSummary(since, to, top=top)
    queryset = CSPReport.objects.filter(created__range=(since, to))
    valid_queryset = queryset.filter(is_valid=True)
    invalid_queryset = queryset.filter(is_valid=False)

    summary.total_count = queryset.count()
    summary.valid_count = valid_queryset.count()

    # Collect sources
    sources = {}
    for report in valid_queryset:
        root_uri = get_root_uri(report.document_uri)
        info = sources.setdefault(root_uri, ViolationInfo(root_uri))
        info.append(report)
    summary.sources = sorted(sources.values(), key=attrgetter('count'), reverse=True)[:top]

    # Collect blocks
    blocks = {}
    for report in valid_queryset:
        root_uri = get_root_uri(report.blocked_uri)
        info = blocks.setdefault(root_uri, ViolationInfo(root_uri))
        info.append(report)
    summary.blocks = sorted(blocks.values(), key=attrgetter('count'), reverse=True)[:top]

    # Collect invalid reports
    summary.invalid_count = invalid_queryset.count()
    summary.invalid_reports = tuple(invalid_queryset[:top])

    return summary