# -*- coding: utf-8 -*-
#
# Copyright (C) 2004-2023 Edgewall Software
# All rights reserved.
#
# This software is licensed as described in the file COPYING, which
# you should have received as part of this distribution. The terms
# are also available at https://trac.edgewall.org/wiki/TracLicense.
#
# This software consists of voluntary contributions made by many
# individuals. For the exact contribution history, see the revision
# history and logs, available at https://trac.edgewall.org/log/.

import difflib
import io
import os
import re
import unittest

# Python 2.7 `assertMultiLineEqual` calls `safe_repr(..., short=True)`
# which breaks our custom failure display in WikiTestCase.

try:
    from unittest.util import safe_repr
except ImportError:
    pass
else:
    unittest.case.safe_repr = lambda obj, short=False: safe_repr(obj, False)

from trac.test import EnvironmentStub, MockRequest
from trac.util.datefmt import datetime_now, to_utimestamp, utc
from trac.util.text import strip_line_ws, to_unicode
from trac.web.chrome import web_context
from trac.wiki.formatter import (HtmlFormatter, InlineHtmlFormatter,
                                 OutlineFormatter)


class WikiTestCase(unittest.TestCase):

    generate_opts = {}

    def __init__(self, title, input, expected, file, line,
                 setup=None, teardown=None, context=None, default_data=False,
                 enable_components=None, disable_components=None,
                 env_path='', destroying=False):
        unittest.TestCase.__init__(self, 'test')
        self.title = title
        self.input = input
        self.expected = expected
        if file.endswith('.pyc'):
            file = file.replace('.pyc', '.py')
        self.file = file
        self.line = line
        self._setup = setup
        self._teardown = teardown
        self._context = context
        self.context = None
        self._env_kwargs = {'default_data': default_data,
                            'enable': enable_components,
                            'disable': disable_components,
                            'path': env_path, 'destroying': destroying}

    def _create_env(self):
        env = EnvironmentStub(**self._env_kwargs)
        # -- intertrac support
        env.config.set('intertrac', 'genshi.title', "Genshi's Trac")
        env.config.set('intertrac', 'genshi.url', "https://genshi.edgewall.org")
        env.config.set('intertrac', 't', 'trac')
        env.config.set('intertrac', 'th.title', "Trac Hacks")
        env.config.set('intertrac', 'th.url', "http://trac-hacks.org")
        # -- safe schemes
        env.config.set('wiki', 'safe_schemes',
                       'data,file,ftp,http,https,svn,svn+ssh,'
                       'rfc-2396.compatible,rfc-2396+under_score')
        return env

    def setUp(self):
        self.env = self._create_env()
        self.req = MockRequest(self.env, script_name='/')
        context = self._context
        if context:
            if isinstance(self._context, tuple):
                context = web_context(self.req, *self._context)
        else:
            context = web_context(self.req, 'wiki', 'WikiStart')
        self.context = context
        # Remove the following lines in order to discover
        # all the places were we should use the req.href
        # instead of env.href
        self.env.href = self.req.href
        self.env.abs_href = self.req.abs_href
        self.env.db_transaction(
            "INSERT INTO wiki VALUES(%s,%s,%s,%s,%s,%s,%s)",
            ('WikiStart', 1, to_utimestamp(datetime_now(utc)), 'joe',
             '--', 'Entry page', 0))
        if self._setup:
            self._setup(self)

    def tearDown(self):
        self.env.reset_db()
        if self._teardown:
            self._teardown(self)

    def test(self):
        """Testing WikiFormatter"""
        formatter = self.formatter()
        v = str(formatter.generate(**self.generate_opts))
        v = v.replace('\r', '').replace('\u200b', '')  # FIXME: keep ZWSP
        v = strip_line_ws(v, leading=False)
        try:
            self.assertEqual(self.expected, v)
        except AssertionError as e:
            msg = to_unicode(e)
            match = re.match(r"u?'(.*)' != u?'(.*)'", msg)
            if match:
                g1 = ["%s\n" % x for x in match.group(1).split(r'\n')]
                g2 = ["%s\n" % x for x in match.group(2).split(r'\n')]
                expected = ''.join(g1)
                actual = ''.join(g2)
                wiki = repr(self.input).replace(r'\n', '\n')
                diff = ''.join(list(difflib.unified_diff(g1, g2, 'expected',
                                                         'actual')))
                # Tip: sometimes, 'expected' and 'actual' differ only by
                #      whitespace, so it can be useful to visualize them, e.g.
                # expected = expected.replace(' ', '.')
                # actual = actual.replace(' ', '.')
                def info(*args):
                    return '\n========== %s: ==========\n%s' % args
                msg = info('expected', expected)
                msg += info('actual', actual)
                msg += info('wiki', ''.join(wiki))
                msg += info('diff', diff)
            raise AssertionError( # See below for details
                '%s\n\n%s:%s: "%s" (%s flavor)' \
                % (msg, self.file, self.line, self.title, formatter.flavor))

    def formatter(self):
        return HtmlFormatter(self.env, self.context, self.input)

    def shortDescription(self):
        return 'Test ' + self.title


class OneLinerTestCase(WikiTestCase):
    def formatter(self):
        return InlineHtmlFormatter(self.env, self.context, self.input)


class EscapeNewLinesTestCase(WikiTestCase):
    generate_opts = {'escape_newlines': True}

    def formatter(self):
        return HtmlFormatter(self.env, self.context, self.input)


class OutlineTestCase(WikiTestCase):
    def formatter(self):
        class Outliner(object):
            flavor = 'outliner'
            def __init__(self, env, context, input):
                self.outliner = OutlineFormatter(env, context)
                self.input = input
            def generate(self):
                out = io.StringIO()
                self.outliner.format(self.input, out)
                return out.getvalue()
        return Outliner(self.env, self.context, self.input)


def wikisyntax_test_suite(data=None, setup=None, file=None, teardown=None,
                          context=None, default_data=False,
                          enable_components=None, disable_components=None,
                          env_path=None, destroying=False):
    suite = unittest.TestSuite()

    def add_test_cases(data, filename):
        tests = re.compile('^(%s.*)$' % ('=' * 30), re.MULTILINE).split(data)
        next_line = 1
        line = 0
        for title, test in zip(tests[1::2], tests[2::2]):
            title = title.lstrip('=').strip()
            if line != next_line:
                line = next_line
            if not test or test == '\n':
                continue
            next_line += len(test.split('\n')) - 1
            if 'SKIP' in title or 'WONTFIX' in title:
                continue
            blocks = test.split('-' * 30 + '\n')
            if len(blocks) < 5:
                blocks.extend([None] * (5 - len(blocks)))
            input, page, oneliner, page_escape_nl, outline = blocks[:5]
            for cls, expected in [
                    (WikiTestCase, page),
                    (OneLinerTestCase, oneliner and oneliner[:-1]),
                    (EscapeNewLinesTestCase, page_escape_nl),
                    (OutlineTestCase, outline)]:
                if expected:
                    tc = cls(title, input, expected, filename, line,
                             setup, teardown, context, default_data,
                             enable_components, disable_components,
                             env_path, destroying)
                    suite.addTest(tc)

    if data:
        add_test_cases(data, file)
    else:
        if os.path.exists(file):
            with open(file, 'r', encoding='utf-8') as fobj:
                data = fobj.read()
            add_test_cases(data, file)
        else:
            print('no ' + file)

    return suite
