# SPDX-FileCopyrightText: © 2023 Christian BUHTZ <c.buhtz@posteo.jp>
# SPDX-FileCopyrightText: © 2025 Michael Olie <aolie1794@gmail.com>
#
# SPDX-License-Identifier: GPL-3.0-only
#
# This file is part of the program "Hyperorg" which is released under GNU
# General Public License v3 (GPLv3).
# See folder LICENSES or go to <https://www.gnu.org/licenses/#GPL>.
"""Tests related to content.py.

About Node class in most cases.
"""
import unittest
import datetime
from hyperorg import content
from hyperorg import elements
from hyperorg import textruns
from . import helper


def _reset_node_class():
    content.Node.nodes = {}
    content.Node.heading_ids = {}


class TestNode(unittest.TestCase):
    """Test related to Node class"""
    # pylint: disable=protected-access

    def setUp(self):
        _reset_node_class()

    def test_class_attributes(self):
        """Default class attributes"""
        self.assertEqual(content.Node.nodes, {})
        self.assertEqual(content.Node.heading_ids, {})

    def test_empty(self):
        """empty by default"""
        node = content.Node([], '')
        self.assertEqual(len(node), 0)

    def test_string(self):
        """Strings allowed"""
        randstring = helper.rand_string()
        note = content.Node([randstring], '')
        self.assertEqual(len(note), 1)

    def test_number(self):
        """Numbers not allowed"""
        with self.assertRaises(TypeError):
            content.Node(1)  # pylint: disable=no-value-for-parameter

    def test_inherit_meta_fields(self):
        """Meta fields are inherited by child nodes/subheadings."""

        node_a = content.Node([
            '#+filetags: One "Two zwei" Three',
            '#+title: Titel',
            '#+date: [1982-08-06 18:23]',
            'foobar'],
            '',
            ['aliasA', 'B C'])

        node_b = content.Node([], '', ['alias Y'])

        # parent node
        self.assertEqual(
            node_a._meta,
            {
                'filetags': ['One', 'Two zwei', 'Three'],
                'aliases_org': ['aliasA', 'B C'],
                'aliases_runs': [['aliasA'], ['B C']],
                'date': datetime.datetime(1982, 8, 6, 18, 23),
            })

        # child node (before inheritance)
        self.assertEqual(
            node_b._meta,
            {
                'aliases_org': ['alias Y'],
                'aliases_runs': [['alias Y']]
            }
        )

        # Action!
        node_b.inherit_meta_fields_from(node_a)

        # child node (After inheritance)
        self.assertEqual(
            node_b._meta,
            {
                'aliases_org': ['alias Y'],
                'aliases_runs': [['alias Y']],
                'filetags': ['One', 'Two zwei', 'Three'],
                'date': datetime.datetime(1982, 8, 6, 18, 23),
            }
        )

    def test_org_properties(self):
        """ Buffer wide org properties."""

        node = content.Node(['#+title: Foobar', '#+foo: Bar'], '')

        self.assertEqual(len(node), 0)
        self.assertEqual(node._meta, {'foo': 'Bar'})
        self.assertEqual(node.title_runs, ['Foobar'])
        self.assertEqual(node.title_org, 'Foobar')


class Titles(unittest.TestCase):
    """Titles of the node"""

    # pylint: disable=protected-access

    def test_title(self):
        """Title set via parse lines."""

        node = content.Node([
            '#+title: Foobar', '#+foo: Bar', 'one'], '')

        self.assertEqual(node.title_runs, ['Foobar'])
        self.assertEqual(node.title_org, 'Foobar')

    def test_title_with_markup(self):
        """Title with markup tokens"""

        node = content.Node(['#+title: Foo =bar='], '')

        self.assertEqual(node.title_org, 'Foo =bar=')
        self.assertEqual(
            node.title_runs,
            [
                'Foo ',
                textruns.Markup('bar', textruns.Markup.Type.VERBATIM)
            ]
        )

    def test_alias_titles(self):
        """Alias titles"""
        node = content.Node([], 'filename', ['alias a', 'boo'])

        self.assertEqual(node._meta['aliases_org'], ['alias a', 'boo'])
        self.assertEqual(node._meta['aliases_runs'], [['alias a'], ['boo']])

    def test_no_alias_titles(self):
        """No alias titles"""
        node = content.Node([], 'filename')

        self.assertNotIn('aliases', node._meta)


class Linking(unittest.TestCase):
    """Tests related to links and linking nodes and subheadings"""

    def setUp(self):
        _reset_node_class()

    def test_get_orgid_links(self):
        """Orgids contained in one Node"""
        lines = [
            '#+title: Titel',
            'Paragraph without link',
            '',
            'Link [[id:123][with label]]',
            '',
            'Link without [[id:456]]',
        ]
        sut = content.Node(lines, '')

        result = sut.get_orgid_links()

        self.assertEqual(result, ['123', '456'])

    def test_get_orgid_links_but_without_subheadings(self):
        """A node knows its orgids but not the orgids of its
        child's (subheadings).
        """
        lines = [
            '#+title: Titel',
            'Paragraph without link',
            '',
            'Link [[id:123][with label]]',
        ]
        node = content.Node(lines, '')

        lines = [
            '* Subheading',
            ':PROPERTIES:',
            ':ID:b0572191-4c8d-4834-a240-5853b3d6a2e5',
            ':END:',
            'This [[id:456][link]]',
        ]
        subn = content.Node(lines, '')
        node.add_subheading_content(subn)

        result = node.get_orgid_links()

        self.assertEqual(result, ['123'])


class Backlinking(unittest.TestCase):
    """Tests related to backlinking"""

    def setUp(self):
        _reset_node_class()

    def test_add_backlink(self):
        """Implicit creation of Backlink elements"""

        # create two nodes
        content.Node.nodes['A'] = content.Node(['#+title: A-title'], '')
        content.Node.nodes['X'] = content.Node(['#+title: X-title'], '')

        # they are empty
        self.assertEqual(len(content.Node.nodes['X']), 0)
        self.assertEqual(len(content.Node.nodes['A']), 0)

        # assuming A links to X
        content.Node.nodes['X'].add_backlinks({'A': []})

        # then X should contain a backlink element to A
        self.assertEqual(len(content.Node.nodes['X']), 1)

        # but A shouldn't
        self.assertEqual(len(content.Node.nodes['A']), 0)

        # There is a Backlink element in X
        backlink = content.Node.nodes['X'][0]
        self.assertIsInstance(backlink, elements.Backlinks)

        # its one and only item has one Link run
        link = backlink[0][0]
        self.assertEqual(link.target, 'A')
        self.assertEqual(link.link_type, textruns.Link.Type.ORGID)
        self.assertEqual(link[0], 'A-title')

    def test_add_two_backlinks(self):
        """Backlink element with two links"""

        # create two nodes
        content.Node.nodes['A'] = content.Node(['#+title: A-title'], '')
        content.Node.nodes['B'] = content.Node(['#+title: B-title'], '')
        content.Node.nodes['X'] = content.Node(['#+title: X-title'], '')

        # they are empty
        self.assertEqual(len(content.Node.nodes['X']), 0)
        self.assertEqual(len(content.Node.nodes['A']), 0)
        self.assertEqual(len(content.Node.nodes['B']), 0)

        # assuming A links to X
        content.Node.nodes['X'].add_backlinks({'A': [], 'B': []})

        # There are two Backlink element
        backlink = content.Node.nodes['X'][0]
        self.assertIsInstance(backlink, elements.Backlinks)

        # its one and only item has two Link runs
        self.assertEqual(len(backlink), 2)
