# #region TestSeedPermissions [C:2] [TYPE Module] [SEMANTICS test,rbac,permissions,seed]
# @BRIEF Tests for seed_permissions.py script (mocked DB, no real auth.db).
# @RELATION BINDS_TO -> [SeedPermissionsScript]
# @TEST_EDGE: initial_permissions_structure -> All entries have resource+action
# @TEST_EDGE: initial_permissions_values -> Known permissions present
# @TEST_EDGE: seed_permissions_adds_new -> Creates missing permissions
# @TEST_EDGE: seed_permissions_idempotent -> Skips existing permissions
# @TEST_EDGE: seed_permissions_creates_user_role -> Creates User role if missing
# @TEST_EDGE: seed_permissions_assigns_to_user_role -> Assigns permissions to User role
# @TEST_EDGE: seed_permissions_db_error -> Rollback on failure
import sys
from pathlib import Path
from unittest.mock import MagicMock, patch, PropertyMock

import pytest

sys.path.insert(0, str(Path(__file__).parent.parent.parent / "src"))


# =============================================================================
# INITIAL_PERMISSIONS
# =============================================================================

class TestInitialPermissions:
    """Data integrity tests for INITIAL_PERMISSIONS constant."""

    def test_all_entries_have_resource_and_action(self):
        """Invariant: every permission has both resource and action."""
        from src.scripts.seed_permissions import INITIAL_PERMISSIONS
        for perm in INITIAL_PERMISSIONS:
            assert "resource" in perm, f"Missing resource in {perm}"
            assert "action" in perm, f"Missing action in {perm}"
            assert isinstance(perm["resource"], str), f"resource not str: {perm}"
            assert isinstance(perm["action"], str), f"action not str: {perm}"

    def test_known_permissions_present(self):
        """Structural: expected permissions exist in the set."""
        from src.scripts.seed_permissions import INITIAL_PERMISSIONS
        perm_set = {(p["resource"], p["action"]) for p in INITIAL_PERMISSIONS}

        assert ("admin:users", "READ") in perm_set
        assert ("admin:users", "WRITE") in perm_set
        assert ("plugin:migration", "EXECUTE") in perm_set
        assert ("dataset:session", "APPROVE") in perm_set
        assert ("dataset:execution", "LAUNCH_PROD") in perm_set
        assert ("settings.connections", "MANAGE") in perm_set

    def test_no_duplicate_permissions(self):
        """Invariant: no duplicate (resource, action) pairs."""
        from src.scripts.seed_permissions import INITIAL_PERMISSIONS
        pairs = [(p["resource"], p["action"]) for p in INITIAL_PERMISSIONS]
        assert len(pairs) == len(set(pairs)), "Duplicate permissions found"

    def test_non_empty(self):
        """Structural: INITIAL_PERMISSIONS is not empty."""
        from src.scripts.seed_permissions import INITIAL_PERMISSIONS
        assert len(INITIAL_PERMISSIONS) > 0


# =============================================================================
# seed_permissions
# =============================================================================

class TestSeedPermissions:
    """Tests for seed_permissions.seed_permissions()."""

    @patch("src.scripts.seed_permissions.AuthSessionLocal")
    @patch("src.scripts.seed_permissions.AuthRepository")
    @patch("src.scripts.seed_permissions.Role")
    def test_adds_new_permissions(self, MockRole, MockRepo, MockSession):
        """Happy: creates all missing permissions."""
        from src.scripts.seed_permissions import seed_permissions

        mock_db = MagicMock()
        MockSession.return_value = mock_db

        # No existing permissions (query returns None for all)
        mock_db.query.return_value.filter.return_value.first.return_value = None

        # Mock AuthRepository
        mock_repo = MagicMock()
        MockRepo.return_value = mock_repo

        # Mock get_role_by_name to return None -> creates User role
        mock_repo.get_role_by_name.return_value = None

        # Mock get_permission_by_resource_action to return a permission
        mock_repo.get_permission_by_resource_action.return_value = MagicMock()

        # Add a User role
        mock_user_role = MagicMock()
        mock_user_role.permissions = []
        MockRole.return_value = mock_user_role

        seed_permissions()

        # Verify permissions were added to DB
        assert mock_db.add.call_count > 0
        mock_db.commit.assert_called()
        mock_db.close.assert_called_once()

    @patch("src.scripts.seed_permissions.AuthSessionLocal")
    @patch("src.scripts.seed_permissions.AuthRepository")
    @patch("src.scripts.seed_permissions.Role")
    def test_idempotent_skips_existing(self, MockRole, MockRepo, MockSession):
        """Edge: existing permissions are skipped (no duplicates)."""
        from src.scripts.seed_permissions import seed_permissions

        mock_db = MagicMock()
        MockSession.return_value = mock_db

        # All permissions already exist
        mock_db.query.return_value.filter.return_value.first.return_value = MagicMock()

        mock_repo = MagicMock()
        MockRepo.return_value = mock_repo
        mock_repo.get_role_by_name.return_value = None

        mock_user_role = MagicMock()
        mock_user_role.permissions = []
        MockRole.return_value = mock_user_role

        mock_repo.get_permission_by_resource_action.return_value = MagicMock()

        seed_permissions()

        # No new permissions should be added (all exist)
        # add() may still be called for the User role, but not for permissions
        mock_db.commit.assert_called()
        mock_db.close.assert_called_once()

    @patch("src.scripts.seed_permissions.AuthSessionLocal")
    @patch("src.scripts.seed_permissions.AuthRepository")
    @patch("src.scripts.seed_permissions.Role")
    def test_db_error_rollback(self, MockRole, MockRepo, MockSession):
        """Edge: DB error triggers rollback."""
        from src.scripts.seed_permissions import seed_permissions

        mock_db = MagicMock()
        MockSession.return_value = mock_db

        mock_db.query.return_value.filter.return_value.first.return_value = None

        mock_repo = MagicMock()
        MockRepo.return_value = mock_repo
        mock_repo.get_role_by_name.side_effect = Exception("DB fail")

        seed_permissions()

        mock_db.rollback.assert_called_once()
        mock_db.close.assert_called_once()
# #endregion TestSeedPermissions
