# This file is part of Indico.
# Copyright (C) 2002 - 2026 CERN
#
# Indico is free software; you can redistribute it and/or
# modify it under the terms of the MIT License; see the
# LICENSE file for more details.

import pytest
from sqlalchemy.orm import undefer

from indico.core.db.sqlalchemy.protection import ProtectionMode
from indico.modules.categories import Category
from indico.modules.categories.models.categories import EventCreationMode


@pytest.mark.parametrize(('protection_mode', 'creation_mode', 'acl', 'allowed'), (
    # not restricted
    (ProtectionMode.public, EventCreationMode.open, None, True),
    (ProtectionMode.protected, EventCreationMode.open, None, False),
    (ProtectionMode.protected, EventCreationMode.open, {'read_access': True}, True),
    # restricted - authorized
    (ProtectionMode.protected, EventCreationMode.restricted, {'full_access': True}, True),
    (ProtectionMode.protected, EventCreationMode.restricted, {'permissions': {'create'}}, True),
    # restricted - not authorized
    (ProtectionMode.public, EventCreationMode.restricted, None, False),
    (ProtectionMode.protected, EventCreationMode.restricted, None, False),
    (ProtectionMode.protected, EventCreationMode.restricted, {'read_access': True}, False)
))
def test_can_create_events(dummy_category, dummy_user, protection_mode, creation_mode, acl, allowed):
    dummy_category.protection_mode = protection_mode
    dummy_category.event_creation_mode = creation_mode
    if acl:
        dummy_category.update_principal(dummy_user, **acl)
    assert dummy_category.can_create_events(dummy_user) == allowed


def test_can_create_events_no_user(dummy_category):
    assert not dummy_category.can_create_events(None)


def test_effective_protection_mode(db):
    def _cat(id_, protection_mode=ProtectionMode.inheriting, children=None):
        return Category(id=id_, title=f'cat-{id_}', protection_mode=protection_mode, children=children or [])
    root = Category.get_root()
    root.protection_mode = ProtectionMode.protected
    root.children = [
        _cat(1),
        _cat(2, ProtectionMode.public, children=[
            _cat(3, children=[
                _cat(4, ProtectionMode.inheriting),
                _cat(5, ProtectionMode.public),
                _cat(6, ProtectionMode.protected),
            ]),
            _cat(7, ProtectionMode.protected, children=[
                _cat(8, ProtectionMode.inheriting),
                _cat(9, ProtectionMode.public),
                _cat(10, ProtectionMode.protected),
            ]),
            _cat(11)
        ])
    ]
    db.session.add(root)
    db.session.flush()
    data = {c.id: c.effective_protection_mode for c in Category.query.options(undefer('effective_protection_mode'))}
    assert data == {
        0: ProtectionMode.protected,
        1: ProtectionMode.protected,
        2: ProtectionMode.public,
        3: ProtectionMode.public,
        4: ProtectionMode.public,
        5: ProtectionMode.public,
        6: ProtectionMode.protected,
        7: ProtectionMode.protected,
        8: ProtectionMode.protected,
        9: ProtectionMode.public,
        10: ProtectionMode.protected,
        11: ProtectionMode.public
    }


@pytest.fixture
def category_family(create_category, db):
    grandpa = Category.get_root()
    dad = create_category(1, title='Dad', parent=grandpa)
    son = create_category(2, title='Son', parent=dad)
    sibling = create_category(3, title='Sibling', parent=dad)

    db.session.add(son)
    db.session.flush()

    return grandpa, dad, son, sibling


def test_nth_parent(category_family):
    grandpa, dad, son, sibling = category_family

    assert son.nth_parent(0) == son
    assert son.nth_parent(1) == dad
    assert son.nth_parent(2) == grandpa
    assert dad.nth_parent(0) == dad
    assert dad.nth_parent(1) == grandpa

    assert sibling.nth_parent(2) == grandpa
    assert sibling.nth_parent(0) != son.nth_parent(0)
    assert sibling.nth_parent(1) == son.nth_parent(1)
    assert sibling.nth_parent(2) == son.nth_parent(2)

    with pytest.raises(IndexError):
        grandpa.nth_parent(1)
    with pytest.raises(IndexError):
        dad.nth_parent(2)
    with pytest.raises(IndexError):
        son.nth_parent(3)
    with pytest.raises(IndexError):
        grandpa.nth_parent(100)


def test_is_descendant_of(category_family):
    grandpa, dad, son = category_family[:3]

    assert not dad.is_descendant_of(dad)
    assert not dad.is_descendant_of(son)
    assert son.is_descendant_of(dad)
    assert son.is_descendant_of(grandpa)
    assert dad.is_descendant_of(grandpa)


def test_visibility_horizon_default(category_family):
    grandpa, dad, son = category_family[:3]

    # default settings, no overriding
    assert son.own_visibility_horizon == grandpa
    assert dad.own_visibility_horizon == grandpa
    assert grandpa.own_visibility_horizon == grandpa
    assert son.real_visibility_horizon == grandpa
    assert dad.real_visibility_horizon == grandpa
    assert grandpa.real_visibility_horizon == grandpa


def test_visibility_horizon_limitation(category_family, create_category):
    grandpa, dad, son, sibling = category_family
    grandson = create_category(4, title='Grandson', parent=son)

    son.visibility = 1  # itself

    assert son.own_visibility_horizon == son.real_visibility_horizon
    assert son.own_visibility_horizon == son

    son.visibility = 2  # dad

    # 'son' and 'grandson' shouldn't be visible from above 'dad'
    assert son.real_visibility_horizon == dad
    assert grandson.real_visibility_horizon == dad

    # sibling should be visible from top, though
    assert sibling.real_visibility_horizon == grandpa

    dad.visibility = 1  # everything limited to 'dad'

    assert son.real_visibility_horizon == dad
    assert grandson.real_visibility_horizon == dad
    assert sibling.real_visibility_horizon == dad
