#  Copyright 2016-2026. Couchbase, Inc.
#  All Rights Reserved.
#
#  Licensed under the Apache License, Version 2.0 (the "License")
#  you may not use this file except in compliance with the License.
#  You may obtain a copy of the License at
#
#      http://www.apache.org/licenses/LICENSE-2.0
#
#  Unless required by applicable law or agreed to in writing, software
#  distributed under the License is distributed on an "AS IS" BASIS,
#  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
#  See the License for the specific language governing permissions and
#  limitations under the License.

from __future__ import annotations

from datetime import timedelta

import pytest
import pytest_asyncio

from couchbase.exceptions import InvalidArgumentException
from couchbase.logic.observability import OpName
from couchbase.management.buckets import CreateBucketSettings
from couchbase.management.collections import UpdateCollectionSettings
from couchbase.management.options import (BuildDeferredQueryIndexOptions,
                                          CreateBucketOptions,
                                          CreateCollectionOptions,
                                          CreatePrimaryQueryIndexOptions,
                                          CreateQueryIndexOptions,
                                          CreateScopeOptions,
                                          DropBucketOptions,
                                          DropCollectionOptions,
                                          DropScopeOptions,
                                          DropSearchIndexOptions,
                                          DropUserOptions,
                                          FlushBucketOptions,
                                          GetAllBucketOptions,
                                          GetAllDatasetOptions,
                                          GetAllDesignDocumentsOptions,
                                          GetAllFunctionOptions,
                                          GetAllQueryIndexOptions,
                                          GetAllScopesOptions,
                                          GetAllSearchIndexesOptions,
                                          GetAllUsersOptions,
                                          GetBucketOptions,
                                          GetSearchIndexOptions,
                                          GetUserOptions,
                                          UpsertSearchIndexOptions,
                                          UpsertUserOptions,
                                          WatchQueryIndexOptions)
from couchbase.management.search import SearchIndex
from couchbase.management.users import Role, User
from couchbase.management.views import DesignDocumentNamespace
from tests.environments.tracing import AsyncManagementTracingEnvironment
from tests.environments.tracing.base_tracing_environment import TracingType


class AsyncManagementTracingTestsSuite:

    TEST_MANIFEST = [
        'test_analytics_mgmt',
        'test_analytics_mgmt_with_parent',
        'test_bucket_mgmt',
        'test_bucket_mgmt_op_no_dispatch_failure',
        'test_bucket_mgmt_with_parent',
        'test_collection_mgmt',
        'test_collection_mgmt_op_no_dispatch_failure',
        'test_collection_mgmt_with_parent',
        'test_eventing_function_mgmt',
        'test_eventing_function_mgmt_with_parent',
        'test_query_index_mgmt',
        'test_query_index_mgmt_op_no_dispatch_failure',
        'test_query_index_mgmt_with_parent',
        'test_search_index_mgmt',
        'test_search_index_mgmt_with_parent',
        'test_user_mgmt',
        'test_user_mgmt_with_parent',
        'test_view_index_mgmt',
        'test_view_index_mgmt_with_parent',
    ]

    @pytest.fixture(autouse=True)
    def reset_validator(self, acb_env: AsyncManagementTracingEnvironment):
        yield
        acb_env.http_span_validator.reset()

    @pytest_asyncio.fixture(scope='class')
    async def num_nodes(self, acb_env):
        if acb_env.cluster._impl._cluster_info is None:
            await acb_env.cluster.cluster_info()
        return len(acb_env.cluster._impl._cluster_info.nodes)

    @pytest.fixture(scope='class')
    def check_multi_node(self, num_nodes):
        if num_nodes == 1:
            pytest.skip('Test only for clusters with more than a single node.')

    @pytest_asyncio.fixture(scope='function')
    async def enable_bucket_mgmt(self, acb_env: AsyncManagementTracingEnvironment):
        await acb_env.enable_tracing_bucket_mgmt()
        yield
        await acb_env.disable_tracing_bucket_mgmt()

    @pytest_asyncio.fixture(scope='function')
    async def enable_collection_mgmt(self, acb_env: AsyncManagementTracingEnvironment):
        await acb_env.enable_tracing_collection_mgmt()
        yield
        await acb_env.disable_tracing_collection_mgmt()

    @pytest_asyncio.fixture(scope='function')
    async def enable_query_index_mgmt(self, acb_env: AsyncManagementTracingEnvironment):
        await acb_env.enable_tracing_query_index_mgmt()
        yield
        await acb_env.disable_tracing_query_index_mgmt()

    @pytest.fixture(scope='function')
    def enable_analytics_mgmt(self, acb_env: AsyncManagementTracingEnvironment):
        acb_env.enable_analytics_mgmt()
        yield
        acb_env.disable_analytics_mgmt()

    @pytest.fixture(scope='function')
    def enable_eventing_mgmt(self, acb_env: AsyncManagementTracingEnvironment):
        acb_env.enable_eventing_mgmt()
        yield
        acb_env.disable_eventing_mgmt()

    @pytest.fixture(scope='function')
    def enable_search_mgmt(self, acb_env: AsyncManagementTracingEnvironment):
        acb_env.enable_search_mgmt()
        yield
        acb_env.disable_search_mgmt()

    @pytest.fixture(scope='function')
    def enable_user_mgmt(self, acb_env: AsyncManagementTracingEnvironment):
        acb_env.enable_user_mgmt()
        yield
        acb_env.disable_user_mgmt()

    @pytest.fixture(scope='function')
    def enable_views_mgmt(self, acb_env: AsyncManagementTracingEnvironment):
        acb_env.enable_views_mgmt()
        yield
        acb_env.disable_views_mgmt()

    @pytest.mark.usefixtures('enable_bucket_mgmt')
    @pytest.mark.asyncio
    async def test_bucket_mgmt(self, acb_env: AsyncManagementTracingEnvironment) -> None:
        bucket_name = acb_env.get_bucket_name()
        validator = acb_env.http_span_validator

        validator.reset(op_name=OpName.BucketCreate, bucket_name=bucket_name)
        settings = CreateBucketSettings(name=bucket_name, ram_quota_mb=100, flush_enabled=False)
        await acb_env.bm.create_bucket(settings)
        validator.validate_http_op()

        # lets make sure the bucket propagates to all nodes
        acb_env.consistency.wait_until_bucket_present(bucket_name)

        validator.reset(op_name=OpName.BucketGet, bucket_name=bucket_name)
        await acb_env.bm.get_bucket(bucket_name)
        validator.validate_http_op()

        validator.reset(op_name=OpName.BucketGetAll)
        await acb_env.bm.get_all_buckets()
        validator.validate_http_op()

        validator.reset(op_name=OpName.BucketCreate, validate_error=True, bucket_name=bucket_name)
        try:
            await acb_env.bm.create_bucket(settings)
        except Exception:
            pass
        validator.validate_http_op()

        validator.reset(op_name=OpName.BucketFlush, validate_error=True, bucket_name=bucket_name)
        try:
            await acb_env.bm.flush_bucket(bucket_name)
        except Exception:
            pass
        validator.validate_http_op()

        validator.reset(op_name=OpName.BucketDrop, bucket_name=bucket_name)
        await acb_env.bm.drop_bucket(bucket_name)
        validator.validate_http_op()

        # lets make sure the bucket is removed from all nodes
        acb_env.consistency.wait_until_bucket_dropped(bucket_name)

        validator.reset(op_name=OpName.BucketDrop, validate_error=True, bucket_name=bucket_name)
        try:
            await acb_env.bm.drop_bucket(bucket_name)
        except Exception:
            pass
        validator.validate_http_op()

        validator.reset(op_name=OpName.BucketGet, validate_error=True, bucket_name=bucket_name)
        try:
            await acb_env.bm.get_bucket(bucket_name)
        except Exception:
            pass
        validator.validate_http_op()

    @pytest.mark.usefixtures('enable_bucket_mgmt')
    @pytest.mark.asyncio
    async def test_bucket_mgmt_op_no_dispatch_failure(self, acb_env: AsyncManagementTracingEnvironment) -> None:
        # so we don't need to setup/teardown; these all fail fast
        bm_ops = [
            (OpName.BucketCreate, True),
            (OpName.BucketDescribe, False),
            (OpName.BucketDrop, False),
            (OpName.BucketFlush, False),
            (OpName.BucketGet, False),
            (OpName.BucketUpdate, True)
        ]

        for op_name, has_settings in bm_ops:
            validator = acb_env.http_span_validator
            validator.reset(op_name=op_name,
                            validate_error=True,
                            error_before_dispatch=True)

            if op_name == OpName.BucketDescribe:
                bm_op_name = 'bucket_describe'
            else:
                bm_op_name = op_name.value.replace('manager_buckets_', '')
            operation = getattr(acb_env.bm, bm_op_name)
            try:
                if has_settings:
                    settings = CreateBucketSettings(name=acb_env.get_bucket_name(),
                                                    ram_quota_mb=100,
                                                    flush_enabled=False)
                    settings.pop('name')  # will trigger a KeyError
                    await operation(settings)
                else:
                    await operation('')
            except (InvalidArgumentException, KeyError):
                pass

            validator.validate_http_op()

    @pytest.mark.usefixtures('enable_bucket_mgmt')
    @pytest.mark.asyncio
    async def test_bucket_mgmt_with_parent(self, acb_env: AsyncManagementTracingEnvironment) -> None:
        bucket_name = acb_env.get_bucket_name()
        validator = acb_env.http_span_validator

        op_name = OpName.BucketCreate
        acb_env.tracer.clear_spans()
        parent_span = acb_env.tracer.request_span(f'parent_{op_name.value}_span')
        validator.reset(op_name=op_name, do_not_clear_spans=True, parent_span=parent_span, bucket_name=bucket_name)
        settings = CreateBucketSettings(name=bucket_name, ram_quota_mb=100, flush_enabled=False)
        await acb_env.bm.create_bucket(settings, CreateBucketOptions(parent_span=parent_span))
        validator.validate_http_op(end_parent=True)

        # lets make sure the bucket propagates to all nodes
        acb_env.consistency.wait_until_bucket_present(bucket_name)

        op_name = OpName.BucketGet
        acb_env.tracer.clear_spans()
        parent_span = acb_env.tracer.request_span(f'parent_{op_name.value}_span')
        validator.reset(op_name=op_name, do_not_clear_spans=True, parent_span=parent_span, bucket_name=bucket_name)
        await acb_env.bm.get_bucket(bucket_name, GetBucketOptions(parent_span=parent_span))
        validator.validate_http_op(end_parent=True)

        op_name = OpName.BucketGetAll
        validator.reset()  # clear out spans and the bucket_name which we don't use for get_all_buckets
        parent_span = acb_env.tracer.request_span(f'parent_{op_name.value}_span')
        validator.reset(op_name=op_name, do_not_clear_spans=True, parent_span=parent_span)
        await acb_env.bm.get_all_buckets(GetAllBucketOptions(parent_span=parent_span))
        validator.validate_http_op(end_parent=True)

        op_name = OpName.BucketCreate
        acb_env.tracer.clear_spans()
        parent_span = acb_env.tracer.request_span(f'parent_{op_name.value}_span')
        validator.reset(op_name=op_name,
                        do_not_clear_spans=True,
                        parent_span=parent_span,
                        validate_error=True,
                        bucket_name=bucket_name)
        try:
            await acb_env.bm.create_bucket(settings, CreateBucketOptions(parent_span=parent_span))
        except Exception:
            pass
        validator.validate_http_op(end_parent=True)

        op_name = OpName.BucketFlush
        acb_env.tracer.clear_spans()
        parent_span = acb_env.tracer.request_span(f'parent_{op_name.value}_span')
        validator.reset(op_name=op_name,
                        do_not_clear_spans=True,
                        parent_span=parent_span,
                        validate_error=True,
                        bucket_name=bucket_name)
        try:
            await acb_env.bm.flush_bucket(bucket_name, FlushBucketOptions(parent_span=parent_span))
        except Exception:
            pass
        validator.validate_http_op(end_parent=True)

        op_name = OpName.BucketDrop
        acb_env.tracer.clear_spans()
        parent_span = acb_env.tracer.request_span(f'parent_{op_name.value}_span')
        # sense we cannot clear the spans, we need to make sure we are not validating the error
        validator.reset(op_name=op_name,
                        do_not_clear_spans=True,
                        parent_span=parent_span,
                        validate_error=False,
                        bucket_name=bucket_name)
        await acb_env.bm.drop_bucket(bucket_name, DropBucketOptions(parent_span=parent_span))
        validator.validate_http_op(end_parent=True)

        # lets make sure the bucket is removed from all nodes
        acb_env.consistency.wait_until_bucket_dropped(bucket_name)

        op_name = OpName.BucketDrop
        acb_env.tracer.clear_spans()
        parent_span = acb_env.tracer.request_span(f'parent_{op_name.value}_span')
        validator.reset(op_name=op_name,
                        do_not_clear_spans=True,
                        parent_span=parent_span,
                        validate_error=True,
                        bucket_name=bucket_name)
        try:
            await acb_env.bm.drop_bucket(bucket_name, DropBucketOptions(parent_span=parent_span))
        except Exception:
            pass
        validator.validate_http_op(end_parent=True)

        op_name = OpName.BucketGet
        acb_env.tracer.clear_spans()
        parent_span = acb_env.tracer.request_span(f'parent_{op_name.value}_span')
        validator.reset(op_name=op_name,
                        do_not_clear_spans=True,
                        parent_span=parent_span,
                        validate_error=True,
                        bucket_name=bucket_name)
        try:
            await acb_env.bm.get_bucket(bucket_name, GetBucketOptions(parent_span=parent_span))
        except Exception:
            pass
        validator.validate_http_op(end_parent=True)

    @pytest.mark.usefixtures('enable_collection_mgmt')
    @pytest.mark.asyncio
    async def test_collection_mgmt(self, acb_env: AsyncManagementTracingEnvironment) -> None:
        validator = acb_env.http_span_validator
        bucket_name = acb_env.test_bucket.name
        scope_name = acb_env.get_scope_name()
        collection_name = acb_env.get_collection_name()

        validator.reset(op_name=OpName.ScopeCreate, bucket_name=bucket_name, scope_name=scope_name)
        await acb_env.test_bucket_cm.create_scope(scope_name)
        validator.validate_http_op()
        acb_env.consistency.wait_until_scope_present(bucket_name, scope_name)

        validator.reset(op_name=OpName.ScopeGetAll, bucket_name=bucket_name)
        await acb_env.test_bucket_cm.get_all_scopes()
        validator.validate_http_op()

        validator.reset(op_name=OpName.ScopeCreate,
                        validate_error=True,
                        bucket_name=bucket_name,
                        scope_name=scope_name)
        try:
            await acb_env.test_bucket_cm.create_scope(scope_name)
        except Exception:
            pass
        validator.validate_http_op()

        validator.reset(op_name=OpName.CollectionCreate,
                        bucket_name=bucket_name,
                        scope_name=scope_name,
                        collection_name=collection_name)
        await acb_env.test_bucket_cm.create_collection(scope_name, collection_name)
        validator.validate_http_op()
        acb_env.consistency.wait_until_collection_present(acb_env.test_bucket.name, scope_name, collection_name)

        validator.reset(op_name=OpName.CollectionCreate,
                        validate_error=True,
                        bucket_name=bucket_name,
                        scope_name=scope_name,
                        collection_name=collection_name)
        try:
            await acb_env.test_bucket_cm.create_collection(scope_name, collection_name)
        except Exception:
            pass
        validator.validate_http_op()

        validator.reset(op_name=OpName.CollectionDrop,
                        bucket_name=bucket_name,
                        scope_name=scope_name,
                        collection_name=collection_name)
        await acb_env.test_bucket_cm.drop_collection(scope_name, collection_name)
        validator.validate_http_op()
        acb_env.consistency.wait_until_collection_dropped(acb_env.test_bucket.name, scope_name, collection_name)

        validator.reset(op_name=OpName.ScopeDrop, bucket_name=bucket_name, scope_name=scope_name)
        await acb_env.test_bucket_cm.drop_scope(scope_name)
        validator.validate_http_op()
        acb_env.consistency.wait_until_scope_dropped(acb_env.test_bucket.name, scope_name)

        validator.reset(op_name=OpName.CollectionDrop,
                        validate_error=True,
                        bucket_name=bucket_name,
                        scope_name=scope_name,
                        collection_name=collection_name)
        try:
            await acb_env.test_bucket_cm.drop_collection(scope_name, collection_name)
        except Exception:
            pass
        validator.validate_http_op()

        validator.reset(op_name=OpName.ScopeDrop, validate_error=True, bucket_name=bucket_name, scope_name=scope_name)
        try:
            await acb_env.test_bucket_cm.drop_scope(scope_name)
        except Exception:
            pass
        validator.validate_http_op()

    @pytest.mark.usefixtures('enable_collection_mgmt')
    @pytest.mark.asyncio
    async def test_collection_mgmt_op_no_dispatch_failure(self, acb_env: AsyncManagementTracingEnvironment) -> None:
        # so we don't need to setup/teardown; these all fail fast
        cm_ops = [(OpName.ScopeCreate, False),
                  (OpName.ScopeDrop, False),
                  (OpName.CollectionCreate, True),
                  (OpName.CollectionDrop, True),
                  (OpName.CollectionUpdate, True)]

        for op_name, has_collection_name in cm_ops:
            validator = acb_env.http_span_validator
            # the bucket name is set internally and will be applied to the span
            validator.reset(op_name=op_name,
                            validate_error=True,
                            error_before_dispatch=True,
                            bucket_name=acb_env.test_bucket.name)

            cm_op_name = op_name.value.replace('manager_collections_', '')
            operation = getattr(acb_env.test_bucket_cm, cm_op_name)
            try:
                if op_name == OpName.CollectionUpdate:
                    settings = UpdateCollectionSettings()
                    await operation('', '', settings)
                elif has_collection_name:
                    await operation('', '')
                else:
                    await operation('')
            except InvalidArgumentException:
                pass

            validator.validate_http_op()

    @pytest.mark.usefixtures('enable_collection_mgmt')
    @pytest.mark.asyncio
    async def test_collection_mgmt_with_parent(self, acb_env: AsyncManagementTracingEnvironment) -> None:
        validator = acb_env.http_span_validator
        bucket_name = acb_env.test_bucket.name
        scope_name = acb_env.get_scope_name()
        collection_name = acb_env.get_collection_name()

        op_name = OpName.ScopeCreate
        validator.reset()  # full reset to clear spans, bucket, scope & collection names
        parent_span = acb_env.tracer.request_span(f'parent_{op_name.value}_span')
        validator.reset(op_name=op_name,
                        do_not_clear_spans=True,
                        parent_span=parent_span,
                        bucket_name=bucket_name,
                        scope_name=scope_name)
        await acb_env.test_bucket_cm.create_scope(scope_name, CreateScopeOptions(parent_span=parent_span))
        validator.validate_http_op(end_parent=True)
        acb_env.consistency.wait_until_scope_present(bucket_name, scope_name)

        op_name = OpName.ScopeGetAll
        validator.reset()  # full reset to clear spans, bucket, scope & collection names
        parent_span = acb_env.tracer.request_span(f'parent_{op_name.value}_span')
        validator.reset(op_name=op_name,
                        do_not_clear_spans=True,
                        parent_span=parent_span,
                        bucket_name=bucket_name)
        await acb_env.test_bucket_cm.get_all_scopes(GetAllScopesOptions(parent_span=parent_span))
        validator.validate_http_op(end_parent=True)

        op_name = OpName.ScopeCreate
        validator.reset()  # full reset to clear spans, bucket, scope & collection names
        parent_span = acb_env.tracer.request_span(f'parent_{op_name.value}_span')
        validator.reset(op_name=op_name,
                        do_not_clear_spans=True,
                        parent_span=parent_span,
                        validate_error=True,
                        bucket_name=bucket_name,
                        scope_name=scope_name)
        try:
            await acb_env.test_bucket_cm.create_scope(scope_name, CreateScopeOptions(parent_span=parent_span))
        except Exception:
            pass
        validator.validate_http_op(end_parent=True)

        op_name = OpName.CollectionCreate
        validator.reset()  # full reset to clear spans, bucket, scope & collection names
        parent_span = acb_env.tracer.request_span(f'parent_{op_name.value}_span')
        # since we cannot clear the spans, we need to make sure we are not validating the error
        validator.reset(op_name=op_name,
                        do_not_clear_spans=True,
                        parent_span=parent_span,
                        bucket_name=bucket_name,
                        scope_name=scope_name,
                        collection_name=collection_name)
        await acb_env.test_bucket_cm.create_collection(scope_name,
                                                       collection_name,
                                                       CreateCollectionOptions(parent_span=parent_span))
        validator.validate_http_op(end_parent=True)
        acb_env.consistency.wait_until_collection_present(acb_env.test_bucket.name, scope_name, collection_name)

        op_name = OpName.CollectionCreate
        validator.reset()  # full reset to clear spans, bucket, scope & collection names
        parent_span = acb_env.tracer.request_span(f'parent_{op_name.value}_span')
        validator.reset(op_name=op_name,
                        do_not_clear_spans=True,
                        parent_span=parent_span,
                        validate_error=True,
                        bucket_name=bucket_name,
                        scope_name=scope_name,
                        collection_name=collection_name)
        try:
            await acb_env.test_bucket_cm.create_collection(scope_name,
                                                           collection_name,
                                                           CreateCollectionOptions(parent_span=parent_span))
        except Exception:
            pass
        validator.validate_http_op(end_parent=True)

        op_name = OpName.CollectionDrop
        validator.reset()  # full reset to clear spans, bucket, scope & collection names
        parent_span = acb_env.tracer.request_span(f'parent_{op_name.value}_span')
        validator.reset(op_name=op_name,
                        do_not_clear_spans=True,
                        parent_span=parent_span,
                        bucket_name=bucket_name,
                        scope_name=scope_name,
                        collection_name=collection_name)
        await acb_env.test_bucket_cm.drop_collection(scope_name,
                                                     collection_name,
                                                     DropCollectionOptions(parent_span=parent_span))
        validator.validate_http_op(end_parent=True)
        acb_env.consistency.wait_until_collection_dropped(acb_env.test_bucket.name, scope_name, collection_name)

        op_name = OpName.ScopeDrop
        validator.reset()  # full reset to clear spans, bucket, scope & collection names
        parent_span = acb_env.tracer.request_span(f'parent_{op_name.value}_span')
        validator.reset(op_name=op_name,
                        do_not_clear_spans=True,
                        parent_span=parent_span,
                        bucket_name=bucket_name,
                        scope_name=scope_name)
        await acb_env.test_bucket_cm.drop_scope(scope_name, DropScopeOptions(parent_span=parent_span))
        validator.validate_http_op(end_parent=True)
        acb_env.consistency.wait_until_scope_dropped(acb_env.test_bucket.name, scope_name)

        op_name = OpName.CollectionDrop
        validator.reset()  # full reset to clear spans, bucket, scope & collection names
        parent_span = acb_env.tracer.request_span(f'parent_{op_name.value}_span')
        validator.reset(op_name=op_name,
                        do_not_clear_spans=True,
                        parent_span=parent_span,
                        validate_error=True,
                        bucket_name=bucket_name,
                        scope_name=scope_name,
                        collection_name=collection_name)
        try:
            await acb_env.test_bucket_cm.drop_collection(scope_name,
                                                         collection_name,
                                                         DropCollectionOptions(parent_span=parent_span))
        except Exception:
            pass
        validator.validate_http_op(end_parent=True)

        op_name = OpName.ScopeDrop
        validator.reset()  # full reset to clear spans, bucket, scope & collection names
        parent_span = acb_env.tracer.request_span(f'parent_{op_name.value}_span')
        validator.reset(op_name=op_name,
                        do_not_clear_spans=True,
                        parent_span=parent_span,
                        validate_error=True,
                        bucket_name=bucket_name,
                        scope_name=scope_name)
        try:
            await acb_env.test_bucket_cm.drop_scope(scope_name, DropScopeOptions(parent_span=parent_span))
        except Exception:
            pass
        validator.validate_http_op(end_parent=True)

    @pytest.mark.usefixtures('enable_query_index_mgmt')
    @pytest.mark.asyncio
    async def test_query_index_mgmt(self, acb_env: AsyncManagementTracingEnvironment) -> None:
        validator = acb_env.http_span_validator

        validator.reset(op_name=OpName.QueryIndexCreate)
        # depending on how tests are ordered, we may already have an index here, so we ignore failure and
        # just validate the span
        try:
            await acb_env.qixm.create_primary_index(acb_env.bucket.name, deferred=True)
        except Exception:
            validator.reset(op_name=OpName.QueryIndexCreate, do_not_clear_spans=True, validate_error=True)
        validator.validate_http_op()

        validator.reset(op_name=OpName.QueryIndexGetAll)
        ixs = await acb_env.qixm.get_all_indexes(acb_env.bucket.name)
        validator.validate_http_op()
        assert len(ixs) == 1

        validator.reset(op_name=OpName.QueryIndexCreate, validate_error=True)
        try:
            await acb_env.qixm.create_primary_index(acb_env.bucket.name, deferred=True)
        except Exception:
            pass
        validator.validate_http_op()

        # Create a bunch of other indexes
        for n in range(5):
            validator.reset(op_name=OpName.QueryIndexCreate)
            await acb_env.qixm.create_index(acb_env.bucket.name,
                                            'ix{0}'.format(n),
                                            ['fld{0}'.format(n)],
                                            CreateQueryIndexOptions(deferred=True))
            validator.validate_http_op()

        validator.reset(op_name=OpName.QueryIndexGetAll)
        ixs = await acb_env.qixm.get_all_indexes(acb_env.bucket.name)
        assert len(ixs) == 6
        validator.validate_http_op()

        ix_names = list(map(lambda i: i.name, ixs))

        validator.reset(op_name=OpName.QueryIndexCreate, validate_error=True)
        try:
            await acb_env.qixm.create_index(acb_env.bucket.name,
                                            ix_names[0],
                                            ['fld0'],
                                            CreateQueryIndexOptions(deferred=True))
        except Exception:
            pass
        validator.validate_http_op()

        validator.reset(op_name=OpName.QueryIndexBuildDeferred)
        await acb_env.qixm.build_deferred_indexes(acb_env.bucket.name)
        # TODO: this would be complicated to validate, it is deeply nested
        # manager_query_build_deferred_indexes (python)
        # └── manager_query_get_all_deferred_indexes (C++ core)
        #     ├── dispatch_to_server (C++ core)
        #     └── manager_query_build_indexes (C++ core)
        #         └── dispatch_to_server (C++ core)
        # validator.validate_http_op()

        validator.reset(op_name=OpName.QueryIndexWatchIndexes, nested_ops=[OpName.QueryIndexGetAll])
        await acb_env.qixm.watch_indexes(acb_env.bucket.name,
                                         ix_names,
                                         WatchQueryIndexOptions(timeout=timedelta(seconds=30)))  # Should be OK
        validator.validate_http_op()

    @pytest.mark.usefixtures('enable_query_index_mgmt')
    @pytest.mark.asyncio
    async def test_query_index_mgmt_op_no_dispatch_failure(self, acb_env: AsyncManagementTracingEnvironment) -> None:
        # so we don't need to setup/teardown; these all fail fast
        qixm_ops = [('create_index', OpName.QueryIndexCreate),
                    ('create_primary_index', OpName.QueryIndexCreate),
                    ('drop_index', OpName.QueryIndexDrop),
                    ('drop_primary_index', OpName.QueryIndexDrop),
                    ('get_all_indexes', OpName.QueryIndexGetAll),
                    ('build_deferred_indexes', OpName.QueryIndexBuildDeferred),
                    ('watch_indexes', OpName.QueryIndexWatchIndexes)]

        for qixm_op_name, op_name in qixm_ops:
            validator = acb_env.http_span_validator
            validator.reset(op_name=op_name,
                            validate_error=True,
                            error_before_dispatch=True)

            operation = getattr(acb_env.qixm, qixm_op_name)
            try:
                if op_name == OpName.QueryIndexCreate:
                    if qixm_op_name == 'create_index':
                        await operation('', '', ['key1'])
                    else:
                        await operation('')
                elif op_name == OpName.QueryIndexDrop:
                    if qixm_op_name == 'drop_index':
                        await operation('', '')
                    else:
                        await operation('')
                elif op_name == OpName.QueryIndexWatchIndexes:
                    await operation('', [])
                else:
                    await operation(None)
            except InvalidArgumentException:
                pass

            validator.validate_http_op()

    @pytest.mark.usefixtures('enable_query_index_mgmt')
    @pytest.mark.asyncio
    async def test_query_index_mgmt_with_parent(self, acb_env: AsyncManagementTracingEnvironment) -> None:
        validator = acb_env.http_span_validator

        op_name = OpName.QueryIndexCreate
        acb_env.tracer.clear_spans()
        parent_span = acb_env.tracer.request_span(f'parent_{op_name.value}_span')
        validator.reset(op_name=op_name, do_not_clear_spans=True, parent_span=parent_span)
        # depending on how tests are ordered, we may already have an index here, so we ignore failure and
        # just validate the span
        try:
            await acb_env.qixm.create_primary_index(acb_env.bucket.name, deferred=True, parent_span=parent_span)
        except Exception:
            validator.reset(op_name=op_name, do_not_clear_spans=True, parent_span=parent_span, validate_error=True)
        validator.validate_http_op(end_parent=True)

        op_name = OpName.QueryIndexGetAll
        acb_env.tracer.clear_spans()
        parent_span = acb_env.tracer.request_span(f'parent_{op_name.value}_span')
        validator.reset(op_name=op_name, do_not_clear_spans=True, parent_span=parent_span)
        ixs = await acb_env.qixm.get_all_indexes(acb_env.bucket.name, GetAllQueryIndexOptions(parent_span=parent_span))
        validator.validate_http_op(end_parent=True)
        assert len(ixs) == 1

        op_name = OpName.QueryIndexCreate
        acb_env.tracer.clear_spans()
        parent_span = acb_env.tracer.request_span(f'parent_{op_name.value}_span')
        validator.reset(op_name=op_name, do_not_clear_spans=True, parent_span=parent_span, validate_error=True)
        try:
            await acb_env.qixm.create_primary_index(acb_env.bucket.name,
                                                    CreatePrimaryQueryIndexOptions(parent_span=parent_span),
                                                    deferred=True)
        except Exception:
            pass
        validator.validate_http_op(end_parent=True)

        # Create a bunch of other indexes
        op_name = OpName.QueryIndexCreate
        for n in range(5):
            acb_env.tracer.clear_spans()
            parent_span = acb_env.tracer.request_span(f'parent_{op_name.value}_span')
            validator.reset(op_name=op_name, do_not_clear_spans=True, parent_span=parent_span, validate_error=False)
            await acb_env.qixm.create_index(acb_env.bucket.name,
                                            'ix{0}'.format(n),
                                            ['fld{0}'.format(n)],
                                            CreateQueryIndexOptions(deferred=True, parent_span=parent_span))
            validator.validate_http_op(end_parent=True)

        op_name = OpName.QueryIndexGetAll
        acb_env.tracer.clear_spans()
        parent_span = acb_env.tracer.request_span(f'parent_{op_name.value}_span')
        validator.reset(op_name=op_name, do_not_clear_spans=True, parent_span=parent_span)
        ixs = await acb_env.qixm.get_all_indexes(acb_env.bucket.name, GetAllQueryIndexOptions(parent_span=parent_span))
        assert len(ixs) == 6
        validator.validate_http_op(end_parent=True)

        ix_names = list(map(lambda i: i.name, ixs))

        op_name = OpName.QueryIndexCreate
        acb_env.tracer.clear_spans()
        parent_span = acb_env.tracer.request_span(f'parent_{op_name.value}_span')
        validator.reset(op_name=op_name, do_not_clear_spans=True, parent_span=parent_span, validate_error=True)
        try:
            await acb_env.qixm.create_index(acb_env.bucket.name,
                                            ix_names[0],
                                            ['fld0'],
                                            CreateQueryIndexOptions(deferred=True, parent_span=parent_span))
        except Exception:
            pass
        validator.validate_http_op(end_parent=True)

        op_name = OpName.QueryIndexBuildDeferred
        acb_env.tracer.clear_spans()
        parent_span = acb_env.tracer.request_span(f'parent_{op_name.value}_span')
        validator.reset(op_name=op_name, do_not_clear_spans=True, parent_span=parent_span, validate_error=False)
        await acb_env.qixm.build_deferred_indexes(acb_env.bucket.name,
                                                  BuildDeferredQueryIndexOptions(parent_span=parent_span))
        # TODO: this would be complicated to validate, it is deeply nested
        # manager_query_build_deferred_indexes (python)
        # └── manager_query_get_all_deferred_indexes (C++ core)
        #     ├── dispatch_to_server (C++ core)
        #     └── manager_query_build_indexes (C++ core)
        #         └── dispatch_to_server (C++ core)
        # validator.validate_http_op()

        op_name = OpName.QueryIndexWatchIndexes
        acb_env.tracer.clear_spans()
        parent_span = acb_env.tracer.request_span(f'parent_{op_name.value}_span')
        validator.reset(op_name=op_name,
                        do_not_clear_spans=True,
                        parent_span=parent_span,
                        nested_ops=[OpName.QueryIndexGetAll],)
        await acb_env.qixm.watch_indexes(acb_env.bucket.name,
                                         ix_names,
                                         WatchQueryIndexOptions(parent_span=parent_span,
                                                                timeout=timedelta(seconds=30)))  # Should be OK
        validator.validate_http_op(end_parent=True)

    @pytest.mark.usefixtures('enable_analytics_mgmt')
    @pytest.mark.asyncio
    async def test_analytics_mgmt(self, acb_env: AsyncManagementTracingEnvironment) -> None:
        validator = acb_env.http_span_validator
        validator.reset(op_name=OpName.AnalyticsDatasetGetAll)
        await acb_env.aixm.get_all_datasets()
        validator.validate_http_op()

    @pytest.mark.usefixtures('enable_analytics_mgmt')
    @pytest.mark.asyncio
    async def test_analytics_mgmt_with_parent(self, acb_env: AsyncManagementTracingEnvironment) -> None:
        validator = acb_env.http_span_validator

        op_name = OpName.AnalyticsDatasetGetAll
        acb_env.tracer.clear_spans()
        parent_span = acb_env.tracer.request_span(f'parent_{op_name.value}_span')
        validator.reset(op_name=op_name, do_not_clear_spans=True, parent_span=parent_span)
        await acb_env.aixm.get_all_datasets(GetAllDatasetOptions(parent_span=parent_span))
        validator.validate_http_op(end_parent=True)

    @pytest.mark.usefixtures('enable_eventing_mgmt')
    @pytest.mark.asyncio
    async def test_eventing_function_mgmt(self, acb_env: AsyncManagementTracingEnvironment) -> None:
        validator = acb_env.http_span_validator
        validator.reset(op_name=OpName.EventingGetAllFunctions)
        await acb_env.efm.get_all_functions()
        validator.validate_http_op()

    @pytest.mark.usefixtures('enable_eventing_mgmt')
    @pytest.mark.asyncio
    async def test_eventing_function_mgmt_with_parent(self, acb_env: AsyncManagementTracingEnvironment) -> None:
        validator = acb_env.http_span_validator

        op_name = OpName.EventingGetAllFunctions
        acb_env.tracer.clear_spans()
        parent_span = acb_env.tracer.request_span(f'parent_{op_name.value}_span')
        validator.reset(op_name=op_name, do_not_clear_spans=True, parent_span=parent_span)
        await acb_env.efm.get_all_functions(GetAllFunctionOptions(parent_span=parent_span))
        validator.validate_http_op(end_parent=True)

    @pytest.mark.usefixtures('enable_search_mgmt')
    @pytest.mark.asyncio
    async def test_search_index_mgmt(self, acb_env: AsyncManagementTracingEnvironment) -> None:
        validator = acb_env.http_span_validator

        validator.reset(op_name=OpName.SearchIndexUpsert)
        idx = SearchIndex(name='tracing-test-index', source_name='default')
        await acb_env.sixm.upsert_index(idx)
        validator.validate_http_op()

        validator.reset(op_name=OpName.SearchIndexGet)
        await acb_env.sixm.get_index('tracing-test-index')
        validator.validate_http_op()

        validator.reset(op_name=OpName.SearchIndexGetAll)
        await acb_env.sixm.get_all_indexes()
        validator.validate_http_op()

        validator.reset(op_name=OpName.SearchIndexDrop)
        await acb_env.sixm.drop_index('tracing-test-index')
        validator.validate_http_op()

        validator.reset(op_name=OpName.SearchIndexDrop, validate_error=True)
        try:
            await acb_env.sixm.drop_index('tracing-test-index')
        except Exception:
            pass
        validator.validate_http_op()

    @pytest.mark.usefixtures('enable_search_mgmt')
    @pytest.mark.asyncio
    async def test_search_index_mgmt_with_parent(self, acb_env: AsyncManagementTracingEnvironment) -> None:
        validator = acb_env.http_span_validator

        op_name = OpName.SearchIndexUpsert
        acb_env.tracer.clear_spans()
        parent_span = acb_env.tracer.request_span(f'parent_{op_name.value}_span')
        validator.reset(op_name=op_name, do_not_clear_spans=True, parent_span=parent_span)
        idx = SearchIndex(name='tracing-parent-test-index', source_name='default')
        await acb_env.sixm.upsert_index(idx, UpsertSearchIndexOptions(parent_span=parent_span))
        validator.validate_http_op(end_parent=True)

        op_name = OpName.SearchIndexGet
        acb_env.tracer.clear_spans()
        parent_span = acb_env.tracer.request_span(f'parent_{op_name.value}_span')
        validator.reset(op_name=op_name, do_not_clear_spans=True, parent_span=parent_span)
        await acb_env.sixm.get_index('tracing-parent-test-index', GetSearchIndexOptions(parent_span=parent_span))
        validator.validate_http_op(end_parent=True)

        op_name = OpName.SearchIndexGetAll
        acb_env.tracer.clear_spans()
        parent_span = acb_env.tracer.request_span(f'parent_{op_name.value}_span')
        validator.reset(op_name=op_name, do_not_clear_spans=True, parent_span=parent_span)
        await acb_env.sixm.get_all_indexes(GetAllSearchIndexesOptions(parent_span=parent_span))
        validator.validate_http_op(end_parent=True)

        op_name = OpName.SearchIndexDrop
        acb_env.tracer.clear_spans()
        parent_span = acb_env.tracer.request_span(f'parent_{op_name.value}_span')
        validator.reset(op_name=op_name, do_not_clear_spans=True, parent_span=parent_span, validate_error=False)
        await acb_env.sixm.drop_index('tracing-parent-test-index', DropSearchIndexOptions(parent_span=parent_span))
        validator.validate_http_op(end_parent=True)

        op_name = OpName.SearchIndexDrop
        acb_env.tracer.clear_spans()
        parent_span = acb_env.tracer.request_span(f'parent_{op_name.value}_span')
        validator.reset(op_name=op_name, do_not_clear_spans=True, parent_span=parent_span, validate_error=True)
        try:
            await acb_env.sixm.drop_index('tracing-parent-test-index', DropSearchIndexOptions(parent_span=parent_span))
        except Exception:
            pass
        validator.validate_http_op(end_parent=True)

    @pytest.mark.usefixtures('enable_user_mgmt')
    @pytest.mark.asyncio
    async def test_user_mgmt(self, acb_env: AsyncManagementTracingEnvironment) -> None:
        validator = acb_env.http_span_validator

        validator.reset(op_name=OpName.UserUpsert)
        test_user = User(username='tracing-test-user',
                         password='password123!',
                         roles=[Role(name='admin')])
        await acb_env.um.upsert_user(test_user)
        validator.validate_http_op()

        validator.reset(op_name=OpName.UserGet)
        await acb_env.um.get_user('tracing-test-user')
        validator.validate_http_op()

        validator.reset(op_name=OpName.UserGetAll)
        await acb_env.um.get_all_users()
        validator.validate_http_op()

        validator.reset(op_name=OpName.UserDrop)
        await acb_env.um.drop_user('tracing-test-user')
        validator.validate_http_op()

        validator.reset(op_name=OpName.UserDrop, validate_error=True)
        try:
            await acb_env.um.drop_user('tracing-test-user')
        except Exception:
            pass
        validator.validate_http_op()

    @pytest.mark.usefixtures('enable_user_mgmt')
    @pytest.mark.asyncio
    async def test_user_mgmt_with_parent(self, acb_env: AsyncManagementTracingEnvironment) -> None:
        validator = acb_env.http_span_validator

        op_name = OpName.UserUpsert
        acb_env.tracer.clear_spans()
        parent_span = acb_env.tracer.request_span(f'parent_{op_name.value}_span')
        validator.reset(op_name=op_name, do_not_clear_spans=True, parent_span=parent_span)
        test_user = User(username='tracing-parent-test-user',
                         password='password123!',
                         roles=[Role(name='admin')])
        await acb_env.um.upsert_user(test_user, UpsertUserOptions(parent_span=parent_span))
        validator.validate_http_op(end_parent=True)

        op_name = OpName.UserGet
        acb_env.tracer.clear_spans()
        parent_span = acb_env.tracer.request_span(f'parent_{op_name.value}_span')
        validator.reset(op_name=op_name, do_not_clear_spans=True, parent_span=parent_span)
        await acb_env.um.get_user('tracing-parent-test-user', GetUserOptions(parent_span=parent_span))
        validator.validate_http_op(end_parent=True)

        op_name = OpName.UserGetAll
        acb_env.tracer.clear_spans()
        parent_span = acb_env.tracer.request_span(f'parent_{op_name.value}_span')
        validator.reset(op_name=op_name, do_not_clear_spans=True, parent_span=parent_span)
        await acb_env.um.get_all_users(GetAllUsersOptions(parent_span=parent_span))
        validator.validate_http_op(end_parent=True)

        op_name = OpName.UserDrop
        acb_env.tracer.clear_spans()
        parent_span = acb_env.tracer.request_span(f'parent_{op_name.value}_span')
        validator.reset(op_name=op_name, do_not_clear_spans=True, parent_span=parent_span, validate_error=False)
        await acb_env.um.drop_user('tracing-parent-test-user', DropUserOptions(parent_span=parent_span))
        validator.validate_http_op(end_parent=True)

        op_name = OpName.UserDrop
        acb_env.tracer.clear_spans()
        parent_span = acb_env.tracer.request_span(f'parent_{op_name.value}_span')
        validator.reset(op_name=op_name, do_not_clear_spans=True, parent_span=parent_span, validate_error=True)
        try:
            await acb_env.um.drop_user('tracing-parent-test-user', DropUserOptions(parent_span=parent_span))
        except Exception:
            pass
        validator.validate_http_op(end_parent=True)

    @pytest.mark.usefixtures('enable_views_mgmt')
    @pytest.mark.asyncio
    async def test_view_index_mgmt(self, acb_env: AsyncManagementTracingEnvironment) -> None:
        validator = acb_env.http_span_validator
        bucket_name = acb_env.bucket.name
        validator.reset(op_name=OpName.ViewIndexGetAll, bucket_name=bucket_name)
        await acb_env.vixm.get_all_design_documents(DesignDocumentNamespace.PRODUCTION)
        validator.validate_http_op()

    @pytest.mark.usefixtures('enable_views_mgmt')
    @pytest.mark.asyncio
    async def test_view_index_mgmt_with_parent(self, acb_env: AsyncManagementTracingEnvironment) -> None:
        validator = acb_env.http_span_validator
        bucket_name = acb_env.bucket.name

        op_name = OpName.ViewIndexGetAll
        acb_env.tracer.clear_spans()
        parent_span = acb_env.tracer.request_span(f'parent_{op_name.value}_span')
        validator.reset(op_name=op_name, do_not_clear_spans=True, parent_span=parent_span, bucket_name=bucket_name)
        await acb_env.vixm.get_all_design_documents(DesignDocumentNamespace.PRODUCTION,
                                                    GetAllDesignDocumentsOptions(parent_span=parent_span))
        validator.validate_http_op(end_parent=True)


class AsyncManagementTracingTests(AsyncManagementTracingTestsSuite):

    @pytest.fixture(scope='class', autouse=True)
    def manifest_validated(self):
        def valid_test_method(meth):
            attr = getattr(AsyncManagementTracingTests, meth)
            return callable(attr) and not meth.startswith('__') and meth.startswith('test')
        method_list = [meth for meth in dir(AsyncManagementTracingTests) if valid_test_method(meth)]
        test_list = set(AsyncManagementTracingTestsSuite.TEST_MANIFEST).symmetric_difference(method_list)
        if test_list:
            pytest.fail(f'Test manifest not validated.  Missing/extra tests: {test_list}.')

    @pytest_asyncio.fixture(scope='class', name='acb_env', params=[TracingType.ThresholdLogging,
                                                                   TracingType.Legacy,
                                                                   TracingType.NoOp,
                                                                   TracingType.Modern
                                                                   ])
    async def couchbase_test_environment(self, acb_base_env, request):
        # a new environment and cluster is created
        acb_env = await AsyncManagementTracingEnvironment.from_environment(acb_base_env, tracing_type=request.param)
        await acb_env.setup(num_docs=50)
        yield acb_env
        await acb_env.teardown()
