# -*- coding: utf-8 -*-
#
# Copyright (C) 2026 XiVO Solutions
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>

from hamcrest import (
    assert_that,
    contains_exactly,
    contains_inanyorder,
    empty,
    equal_to,
    has_entries,
    not_none,
)
from sqlalchemy.exc import IntegrityError

from xivo_dao.alchemy.ctidirectories_by_labels import CtiDirectoriesByLabels
from xivo_dao.resources.directory_by_labels import dao
from xivo_dao.tests.test_dao import DAOTestCase


class TestDirectoryByLabelsConfig(DAOTestCase):

    def test_get_returns_the_singleton_with_its_display(self):
        display = self.add_cti_display(name='Display')
        self.add_me(CtiDirectoriesByLabels(display_id=display.id))

        result = dao.get()

        assert_that(result, not_none())
        assert_that(result.display_id, equal_to(display.id))

    def test_set_display_updates_the_singleton_display(self):
        display1 = self.add_cti_display(name='Display')
        display2 = self.add_cti_display(name='Other')
        self.add_me(CtiDirectoriesByLabels(display_id=display1.id))

        dao.set_display(display2.id)

        assert_that(dao.get().display_id, equal_to(display2.id))


class TestDefaultSources(DAOTestCase):

    def test_add_default_source_then_find_all_returns_it(self):
        directory = self.add_cti_directory(name='xivodir')

        dao.add_default_source(directory.id)

        result = dao.find_all_default_sources()
        assert_that([s.source_id for s in result], contains_exactly(directory.id))

    def test_remove_default_source_removes_it(self):
        directory = self.add_cti_directory(name='xivodir')
        dao.add_default_source(directory.id)

        dao.remove_default_source(directory.id)

        assert_that(dao.find_all_default_sources(), empty())


class TestLabelMapping(DAOTestCase):

    def test_associate_then_find_all_mappings_returns_the_pair(self):
        directory = self.add_cti_directory(name='xivodir')
        label = self.add_label()

        dao.associate(directory, label)

        result = dao.find_all_mappings()
        assert_that(
            [(m.source_id, m.label_id) for m in result],
            contains_exactly((directory.id, label.id)),
        )

    def test_dissociate_removes_the_mapping(self):
        directory = self.add_cti_directory(name='xivodir')
        label = self.add_label()
        dao.associate(directory, label)

        dao.dissociate(directory, label)

        assert_that(dao.find_all_mappings(), empty())


class TestForeignKeyCascade(DAOTestCase):

    def test_deleting_directory_cascades_to_default_sources(self):
        directory = self.add_cti_directory(name='xivodir')
        dao.add_default_source(directory.id)

        self.session.delete(directory)
        self.session.flush()

        assert_that(dao.find_all_default_sources(), empty())

    def test_deleting_directory_cascades_to_label_mapping(self):
        directory = self.add_cti_directory(name='xivodir')
        label = self.add_label()
        dao.associate(directory, label)

        self.session.delete(directory)
        self.session.flush()

        assert_that(dao.find_all_mappings(), empty())

    def test_deleting_label_cascades_to_label_mapping(self):
        directory = self.add_cti_directory(name='xivodir')
        label = self.add_label()
        dao.associate(directory, label)

        self.session.delete(label)
        self.session.flush()

        assert_that(dao.find_all_mappings(), empty())

    def test_deleting_display_referenced_by_singleton_is_blocked(self):
        display = self.add_cti_display(name='Display')
        self.add_me(CtiDirectoriesByLabels(display_id=display.id))

        self.session.delete(display)

        self.assertRaises(IntegrityError, self.session.flush)


class TestGetLookupByLabelConfig(DAOTestCase):

    def test_returns_display_defaults_and_sources_grouped_by_label_id(self):
        display = self.add_cti_display(name='Display')
        self.add_me(CtiDirectoriesByLabels(display_id=display.id))

        xivodir = self.add_cti_directory(name='xivodir')
        internal = self.add_cti_directory(name='internal')
        ldap = self.add_cti_directory(name='ldap42')

        sales = self.add_label(display_name='sales')
        support = self.add_label(display_name='support')

        dao.add_default_source(xivodir.id)
        dao.add_default_source(internal.id)

        dao.associate(xivodir, sales)
        dao.associate(internal, support)
        dao.associate(ldap, support)

        result = dao.get_lookup_by_label_config()

        assert_that(
            result,
            has_entries(
                {
                    'display_id': display.id,
                    'default_sources': contains_inanyorder('xivodir', 'internal'),
                    'labels': has_entries(
                        {
                            sales.id: has_entries(
                                {
                                    'name': 'sales',
                                    'sources': contains_inanyorder('xivodir'),
                                }
                            ),
                            support.id: has_entries(
                                {
                                    'name': 'support',
                                    'sources': contains_inanyorder(
                                        'internal', 'ldap42'
                                    ),
                                }
                            ),
                        }
                    ),
                }
            ),
        )

    def test_returns_empty_collections_when_no_defaults_or_mappings(self):
        display = self.add_cti_display(name='Display')
        self.add_me(CtiDirectoriesByLabels(display_id=display.id))
        # a label that is not mapped to any directory must not appear
        self.add_label(display_name='unmapped')

        result = dao.get_lookup_by_label_config()

        assert_that(
            result,
            equal_to(
                {
                    'display_id': display.id,
                    'default_sources': [],
                    'labels': {},
                }
            ),
        )

    def test_get_display_name_returns_the_singleton_display_name(self):
        display = self.add_cti_display(name='Compact')
        self.add_me(CtiDirectoriesByLabels(display_id=display.id))

        assert_that(dao.get_display_name(), equal_to('Compact'))

    def test_a_directory_mapped_to_two_labels_appears_under_both(self):
        display = self.add_cti_display(name='Display')
        self.add_me(CtiDirectoriesByLabels(display_id=display.id))

        shared = self.add_cti_directory(name='shared')
        sales = self.add_label(display_name='sales')
        support = self.add_label(display_name='support')

        dao.associate(shared, sales)
        dao.associate(shared, support)

        result = dao.get_lookup_by_label_config()

        assert_that(
            result['labels'],
            has_entries(
                {
                    sales.id: has_entries({'sources': contains_exactly('shared')}),
                    support.id: has_entries({'sources': contains_exactly('shared')}),
                }
            ),
        )
