# Copied from CPython. The value here is mostly testing our fallback code
# in Python < 3.14. At some point when we only support Python >= 3.14 this
# test is really just testing Python internals and should be deleted.

import pickle
import unittest
from collections.abc import Iterator, Iterable
try:
    from string.templatelib import Template, Interpolation, convert
except ImportError:
    t"{1}"  # from here, Cython should install Template and Interpolation into string.templatelib
    from string.templatelib import Template, Interpolation
    convert = None
    
############# From test.test_support._string ##############
class TStringBaseCase:
    def assertInterpolationEqual(self, i, exp):
        """Test Interpolation equality.

        The *i* argument must be an Interpolation instance.

        The *exp* argument must be a tuple of the form
        (value, expression, conversion, format_spec) where the final three
        items may be omitted and are assumed to be '', None and '' respectively.
        """
        if len(exp) == 4:
            actual = (i.value, i.expression, i.conversion, i.format_spec)
            self.assertEqual(actual, exp)
        elif len(exp) == 3:
            self.assertEqual((i.value, i.expression, i.conversion), exp)
            self.assertEqual(i.format_spec, "")
        elif len(exp) == 2:
            self.assertEqual((i.value, i.expression), exp)
            self.assertEqual(i.conversion, None)
            self.assertEqual(i.format_spec, "")
        elif len(exp) == 1:
            self.assertEqual((i.value,), exp)
            self.assertEqual(i.expression, "")
            self.assertEqual(i.conversion, None)
            self.assertEqual(i.format_spec, "")

    def assertTStringEqual(self, t, strings, interpolations):
        """Test template string literal equality.

        The *strings* argument must be a tuple of strings equal to *t.strings*.

        The *interpolations* argument must be a sequence of tuples which are
        compared against *t.interpolations*. Each tuple must match the form
        described in the `assertInterpolationEqual` method.
        """
        self.assertEqual(t.strings, strings)
        self.assertEqual(len(t.interpolations), len(interpolations))

        if len(t.interpolations) != len(interpolations):
            # Handle Python <3.10 which doesn't have strict in zip
            raise ValueError(f"Lengths differ {len(t.interpolations)} {len(interpolations)}")
        for i, exp in zip(t.interpolations, interpolations):
            self.assertInterpolationEqual(i, exp)


def _convert(value, conversion):
    if conversion == "a":
        return ascii(value)
    elif conversion == "r":
        return repr(value)
    elif conversion == "s":
        return str(value)
    return value


def fstring(template):
    parts = []
    for item in template:
        # adapted from match/case since we don't yet support it
        if isinstance(item, str):
            parts.append(item)
        elif isinstance(item, Interpolation):
            value = item.value
            conversion = item.conversion
            format_spec = item.format_spec
            value = _convert(value, conversion)
            value = format(value, format_spec)
            parts.append(value)
    return "".join(parts)


############ from test_templatelib.py ###############
class TestTemplate(unittest.TestCase, TStringBaseCase):

    def test_common(self):
        self.assertEqual(type(t'').__name__, 'Template')
        self.assertEqual(type(t'').__qualname__, 'Template')
        self.assertEqual(type(t'').__module__, 'string.templatelib')

        a = 'a'
        i = t'{a}'.interpolations[0]
        self.assertEqual(type(i).__name__, 'Interpolation')
        self.assertEqual(type(i).__qualname__, 'Interpolation')
        self.assertEqual(type(i).__module__, 'string.templatelib')

    def test_final_types(self):
        with self.assertRaisesRegex(TypeError, 'is not an acceptable base type'):
            class Sub(Template): ...

        with self.assertRaisesRegex(TypeError, 'is not an acceptable base type'):
            class Sub(Interpolation): ...

    def test_basic_creation(self):
        # Simple t-string creation
        t = t'Hello, world'
        self.assertIsInstance(t, Template)
        self.assertTStringEqual(t, ('Hello, world',), ())
        self.assertEqual(fstring(t), 'Hello, world')

        # Empty t-string
        t = t''
        self.assertTStringEqual(t, ('',), ())
        self.assertEqual(fstring(t), '')

        # Multi-line t-string
        t = t"""Hello,
world"""
        self.assertEqual(t.strings, ('Hello,\nworld',))
        self.assertEqual(len(t.interpolations), 0)
        self.assertEqual(fstring(t), 'Hello,\nworld')

    def test_interpolation_creation(self):
        i = Interpolation('Maria', 'name', 'a', 'fmt')
        self.assertInterpolationEqual(i, ('Maria', 'name', 'a', 'fmt'))

        i = Interpolation('Maria', 'name', 'a')
        self.assertInterpolationEqual(i, ('Maria', 'name', 'a'))

        i = Interpolation('Maria', 'name')
        self.assertInterpolationEqual(i, ('Maria', 'name'))

        i = Interpolation('Maria')
        self.assertInterpolationEqual(i, ('Maria',))

    def test_creation_interleaving(self):
        # Should add strings on either side
        t = Template(Interpolation('Maria', 'name', None, ''))
        self.assertTStringEqual(t, ('', ''), [('Maria', 'name')])
        self.assertEqual(fstring(t), 'Maria')

        # Should prepend empty string
        t = Template(Interpolation('Maria', 'name', None, ''), ' is my name')
        self.assertTStringEqual(t, ('', ' is my name'), [('Maria', 'name')])
        self.assertEqual(fstring(t), 'Maria is my name')

        # Should append empty string
        t = Template('Hello, ', Interpolation('Maria', 'name', None, ''))
        self.assertTStringEqual(t, ('Hello, ', ''), [('Maria', 'name')])
        self.assertEqual(fstring(t), 'Hello, Maria')

        # Should concatenate strings
        t = Template('Hello', ', ', Interpolation('Maria', 'name', None, ''),
                     '!')
        self.assertTStringEqual(t, ('Hello, ', '!'), [('Maria', 'name')])
        self.assertEqual(fstring(t), 'Hello, Maria!')

        # Should add strings on either side and in between
        t = Template(Interpolation('Maria', 'name', None, ''),
                     Interpolation('Python', 'language', None, ''))
        self.assertTStringEqual(
            t, ('', '', ''), [('Maria', 'name'), ('Python', 'language')]
        )
        self.assertEqual(fstring(t), 'MariaPython')

    def test_template_values(self):
        t = t'Hello, world'
        self.assertEqual(t.values, ())

        name = "Lys"
        t = t'Hello, {name}'
        self.assertEqual(t.values, ("Lys",))

        country = "GR"
        age = 0
        t = t'Hello, {name}, {age} from {country}'
        self.assertEqual(t.values, ("Lys", 0, "GR"))

    def test_pickle_template(self):
        user = 'test'
        for template in (
            t'',
            t"No values",
            t'With inter {user}',
            t'With ! {user!r}',
            t'With format {1 / 0.3:.2f}',
            Template(),
            Template('a'),
            Template(Interpolation('Nikita', 'name', None, '')),
            Template('a', Interpolation('Nikita', 'name', 'r', '')),
        ):
            for proto in range(pickle.HIGHEST_PROTOCOL + 1):
                with self.subTest(proto=proto, template=template):
                    pickled = pickle.dumps(template, protocol=proto)
                    unpickled = pickle.loads(pickled)

                    self.assertEqual(unpickled.values, template.values)
                    self.assertEqual(fstring(unpickled), fstring(template))

    def test_pickle_interpolation(self):
        for interpolation in (
            Interpolation('Nikita', 'name', None, ''),
            Interpolation('Nikita', 'name', 'r', ''),
            Interpolation(1/3, 'x', None, '.2f'),
        ):
            for proto in range(pickle.HIGHEST_PROTOCOL + 1):
                with self.subTest(proto=proto, interpolation=interpolation):
                    pickled = pickle.dumps(interpolation, protocol=proto)
                    unpickled = pickle.loads(pickled)

                    self.assertEqual(unpickled.value, interpolation.value)
                    self.assertEqual(unpickled.expression, interpolation.expression)
                    self.assertEqual(unpickled.conversion, interpolation.conversion)
                    self.assertEqual(unpickled.format_spec, interpolation.format_spec)


class TemplateIterTests(unittest.TestCase):
    def test_abc(self):
        self.assertIsInstance(iter(t''), Iterable)
        self.assertIsInstance(iter(t''), Iterator)

    def test_final(self):
        TemplateIter = type(iter(t''))
        with self.assertRaisesRegex(TypeError, 'is not an acceptable base type'):
            class Sub(TemplateIter): ...

    def test_iter(self):
        x = 1
        res = list(iter(t'abc {x} yz'))

        self.assertEqual(res[0], 'abc ')
        self.assertIsInstance(res[1], Interpolation)
        self.assertEqual(res[1].value, 1)
        self.assertEqual(res[1].expression, 'x')
        self.assertEqual(res[1].conversion, None)
        self.assertEqual(res[1].format_spec, '')
        self.assertEqual(res[2], ' yz')

    def test_exhausted(self):
        # See https://github.com/python/cpython/issues/134119.
        template_iter = iter(t"{1}")
        self.assertIsInstance(next(template_iter), Interpolation)
        self.assertRaises(StopIteration, next, template_iter)
        self.assertRaises(StopIteration, next, template_iter)


class TestFunctions(unittest.TestCase):
    def test_convert(self):
        if convert is None:
            # Cython doesn't patch in 'convert'
            return 
        from fractions import Fraction

        for obj in ('Café', None, 3.14, Fraction(1, 2)):
            with self.subTest(f'{obj=}'):
                self.assertEqual(convert(obj, None), obj)
                self.assertEqual(convert(obj, 's'), str(obj))
                self.assertEqual(convert(obj, 'r'), repr(obj))
                self.assertEqual(convert(obj, 'a'), ascii(obj))

                # Invalid conversion specifier
                with self.assertRaises(ValueError):
                    convert(obj, 'z')
                with self.assertRaises(ValueError):
                    convert(obj, 1)
                with self.assertRaises(ValueError):
                    convert(obj, object())


if __name__ == '__main__':
    unittest.main()
