#!/usr/bin/env python3

"""
Copyright 2017-Present Couchbase, Inc.

Use of this software is governed by the Business Source License included in
the file licenses/BSL-Couchbase.txt.  As of the Change Date specified in that
file, in accordance with the Business Source License, use of this software will
be governed by the Apache License, Version 2.0, included in the file
licenses/APL2.txt.
"""

import time
import sys

import clitool
import cli_auth_utils
import mc_bin_client
import memcacheConstants
import sys

cmd = cli_auth_utils.cmd_decorator

# Define a set of parameters that should not be set via cbepctl
RESTRICTED_PARAMS = {"access_scanner_enabled", "exp_pager_stime"}

@cmd
def set_param(mc, type, key, val, force=False):
    if not force and key in RESTRICTED_PARAMS:
        print(f"Error: Setting '{key}' via cbepctl is not allowed. Use the REST API instead.")
        return

    engine_param = None
    if type == 'checkpoint_param':
        engine_param = memcacheConstants.ENGINE_PARAM_CHECKPOINT
    elif type == 'flush_param':
        engine_param = memcacheConstants.ENGINE_PARAM_FLUSH
    elif type == 'dcp_param':
        engine_param = memcacheConstants.ENGINE_PARAM_DCP
    elif type == 'vbucket_param':
        engine_param = memcacheConstants.ENGINE_PARAM_VBUCKET
    else:
        print('Error: Bad parameter %s' % type)

    if key == 'exp_pager_initial_run_time' and val == 'disable':
        val = '-1'

    if (key == "mem_used_merge_threshold_percent" and
            (float(val) > 100.0 or float(val) < 0.0)):
        print('Error: Invalid mem_used_merge_threshold_percent value %s' % val)
        return
    try:
        mc.set_param(0, key, val, engine_param)
        print('set %s to %s' % (key, val))
    except mc_bin_client.MemcachedError as error:
        print('Error: %s' % error.msg)
    except mc_bin_client.TimeoutError as error:
        print(error)
    except Exception as e:
        print('Generic error (%s)' % e)


@cmd
def set_vbucket_param(mc, key, vbucket, val, force=False):
    engine_param = memcacheConstants.ENGINE_PARAM_VBUCKET

    try:
        mc.set_param(int(vbucket), key, val, engine_param)
        print('set %s to %s' % (key, val))
    except mc_bin_client.MemcachedError as error:
        print('Error: %s' % error.msg)
    except mc_bin_client.TimeoutError as error:
        print(error)
    except Exception as e:
        print('Generic error (%s)' % e)


@cmd
def stop(mc, force=False):
    try:
        mc.stop_persistence()
        stopped = False
        while not stopped:
            time.sleep(0.5)
            try:
                stats = mc.stats()
                success = True
            except BaseException:
                if success:
                    mc = mc_bin_client.MemcachedClient(mc.host, mc.port)
                    raise
                else:
                    raise
            success = False
            if stats['ep_flusher_state'] == 'paused':
                stopped = True
        print('Persistence stopped')
    except mc_bin_client.MemcachedError as error:
        print('Error: %s' % error.msg)
    except mc_bin_client.TimeoutError as error:
        print(error)
    except Exception as e:
        print('Generic error (%s)' % e)


@cmd
def start(mc, force=False):
    try:
        mc.start_persistence()
        print('Persistence started')
    except mc_bin_client.MemcachedError as error:
        print('Error: %s' % error.msg)
    except mc_bin_client.TimeoutError as error:
        print(error)
    except Exception as e:
        print('Generic error (%s)' % e)


@cmd
def drain(mc, force=False):
    try:
        while True:
            s = mc.stats()
            if s['ep_queue_size'] == "0":
                print("done")
                return
            time.sleep(2)
            sys.stdout.write('.')
            sys.stdout.flush()
        print('Write queues drained')
    except mc_bin_client.MemcachedError as error:
        print('Error: %s' % error.msg)
    except mc_bin_client.TimeoutError as error:
        print(error)
    except Exception as e:
        print('Generic error (%s)' % e)


@cmd
def set_collections(mc, filename, force=False):

    try:
        jsonManifestFile = open(filename, "r")
        json = jsonManifestFile.read()
        jsonManifestFile.close()
        print("Setting collections - {}".format(json))
        mc.set_collections(json)

    except mc_bin_client.MemcachedError as error:
        print('Error: %s' % error.msg)
    except mc_bin_client.TimeoutError as error:
        print(error)
    except Exception as e:
        print('Generic error (%s)' % e)


@cmd
def get_collections(mc, force=False):

    try:
        print(mc.get_collections()[2])

    except mc_bin_client.MemcachedError as error:
        print('Error: %s' % error.msg)
    except mc_bin_client.TimeoutError as error:
        print(error)
    except Exception as e:
        print('Generic error (%s)' % e)


@cmd
def run_compaction(mc, vbucket, purgeBeforeTs, purgeBeforeSeq, dropDeletes,
                   force=False):
    engine_param = memcacheConstants.ENGINE_PARAM_VBUCKET

    try:
        mc.compact_db(
            int(vbucket),
            int(purgeBeforeTs),
            int(purgeBeforeSeq),
            int(dropDeletes))
        print('compaction started on vbucket {}'.format(vbucket))
    except mc_bin_client.MemcachedError as error:
        print('Error: %s' % error.msg)
    except mc_bin_client.TimeoutError as error:
        print(error)
    except Exception as e:
        print('Generic error (%s)' % e)


if __name__ == '__main__':

    c = cli_auth_utils.get_authed_clitool("""
Persistence:
  stop           - stop persistence
  start          - start persistence
  drain          - wait until queues are drained


Available params for "set":

  Available params for set checkpoint_param:
    max_checkpoints              - The expected max number of checkpoints in each VBucket on a balanced system.
                                   Note: That is not a hard limit on the single vbucket. That is used (together
                                   with checkpoint_memory_ratio) for computing checkpoint_max_size, which
                                   triggers checkpoint creation.
    checkpoint_memory_ratio      - Max ratio of the bucket quota that can be allocated in checkpoints.
    checkpoint_memory_recovery_upper_mark - Fraction of the checkpoint quota (as computed by checkpoint_memory_ratio) that triggers attempt of memory releasing from checkpoint.
    checkpoint_memory_recovery_lower_mark - Fraction of the checkpoint quota (as computed by checkpoint_memory_ratio) that represents the target of checkpoint memory recovery. Memory recovery yields when reached.
    checkpoint_max_size          - Max size (in bytes) of a single checkpoint. '0' for EPEngine auto-setup.
    checkpoint_destruction_tasks - Number of tasks responsible for destroying closed unreferenced checkpoints.

  Available params for set flush_param:
    alog_sleep_time              - Access scanner interval (minute)
    alog_task_time               - Hour in UTC time when access scanner task is
                                   next scheduled to run (0-23).
    backfill_mem_threshold       - Memory threshold (%) on the current bucket quota
                                   before backfill task is made to back off.
    bfilter_enabled              - Enable or disable bloom filters (true/false)
    bfilter_residency_threshold  - Resident ratio threshold below which all items
                                   will be considered in the bloom filters in full
                                   eviction policy (0.0 - 1.0)
    dcp_min_compression_ratio    - Minimum compression ratio of compressed doc against
                                   the original doc. If compressed doc is greater than
                                   this percentage of the original doc, then the doc
                                   will be shipped as is by the DCP producer if value
                                   compression is enabled by the DCP consumer. Applies
                                   to all producers (Ideal range: 0.0 - 1.0)
    durability_min_level         - Bucket Minimum Durability Level.
                                   KVEngine upgrades any write request to this min-level,
                                   if the min-level is higher than the write-level. May
                                   upgrade a NormalWrite to SyncWrite.
    defragmenter_enabled         - Enable or disable the defragmenter
                                   (true/false).
    defragmenter_mode            - The mode the defragmenter is in static, auto_linear or auto_pid
    defragmenter_interval        - How often defragmenter task should be run
                                   when mode is static (in seconds).
    defragmenter_auto_lower_threshold - When the mode is not static a 'scored'
                                        threshold for when sleep time is recalculated.
                                        (parameter is floating point)
    defragmenter_auto_upper_threshold - When the mode is auto_linear and 'scored' fragmentation
                                        exceeds this, defragmenter_auto_max_sleep is used.
                                        (parameter is floating point)
    defragmenter_auto_max_sleep  - The minimum sleep time when mode is not
                                   static (fractional seconds, e.g. 0.2)
    defragmenter_auto_min_sleep  - The maximum sleep time when mode is not
                                   static (fractional seconds, e.g. 8.5)
    defragmenter_auto_pid_p      - The P term for when mode is auto_pid
                                   (parameter is floating point)
    defragmenter_auto_pid_i      - The I term for when mode is auto_pid
                                   (parameter is floating point)
    defragmenter_auto_pid_d      - The D term for when mode is auto_pid
                                   (parameter is floating point)
    defragmenter_auto_pid_dt     - The interval term for when mode is auto_pid
    defragmenter_age_threshold   - How old (measured in number of defragmenter
                                   passes) must a document be to be considered
                                   for defragmentation.
    defragmenter_stored_value_age_threshold   - How old (measured in number of defragmenter
                                   passes) must a StoredValue (key + meta) be to be considered
                                   for defragmentation.
    defragmenter_chunk_duration  - Maximum time (in ms) defragmentation task
                                   will run for before being paused (and
                                   resumed at the next defragmenter_interval).
    exp_pager_enabled            - Enable expiry pager.
    exp_pager_initial_run_time   - Expiry Pager first task time (UTC)
                                   (Range: 0 - 23, Specify 'disable' to not delay the
                                   the expiry pager, in which case first run will be
                                   after exp_pager_stime seconds.)
    flush_batch_max_bytes         - Max size (in bytes) of a single flush-batch passed to the
                                   KVStore for persistence.
    item_compressor_interval     - How often the item compressor task should be run
                                   (in milliseconds).
    item_compressor_chunk_duration - Maximum time (in ms) the item compressor task
                                   will run for before being paused (and resumed at
                                   the next item compressor interval).
    max_size                     - Max memory used by the server.
    min_compression_ratio        - Minimum compression ratio of uncompressed doc
                                   against the compressed doc. If the ratio happens
                                   to be lesser than this value, then a compressed
                                   document will be stored as an uncompressed.
    mutation_mem_ratio           - Memory ratio of the current bucket quota
                                   for accepting a new mutation.
    timing_log                   - path to log detailed timing stats.
                                   traffic
    retain_erroneous_tombstones  - Whether to retain erroneous tombstones or not.
    xattr_enabled                - Enabled/Disable xattr support for the specified bucket.
                                   Accepted input values are true or false.
    max_ttl                      - A max TTL (1 to 2,147,483,647) to apply to all new
                                   documents (or touched documents). 0 means this is
                                   disabled and the protocol specified expiry value is used.
    mem_used_merge_threshold_percent - A percentage used in calculating the threshold at which
                                   a per core memory counter is accumulated into a global
                                   memory used counter. This configuration parameter generates
                                   a value which is n% of max_size / number of CPUs.
    seqno_persistence_timeout    - Timeout in seconds after which a pending SeqnoPersistence operation is temp-failed
    history_retention_seconds    - Seconds of history a bucket should aim to retain on disk.
    history_retention_bytes      - Max bytes of history a bucket should aim to retain on disk.
    magma_fusion_logstore_uri - URI of the Fusion LogStore
    magma_fusion_metadatastore_uri - URI of the Fusion MetadataStore
    magma_fusion_upload_interval - The interval between kvstore syncs to fusion, in seconds.
    magma_fusion_logstore_fragmentation_threshold - The threshold at which the fusion log store will perform garbage
                                                    collection.

  Available params for "set dcp_param":
    dcp_consumer_process_buffered_messages_yield_limit - The threshold at which
                                                         the Consumer will yield
                                                         when processing items.

    dcp_consumer_process_buffered_messages_batch_size - The number of items the
                                                        DCP processor will consume
                                                        in a single batch.

    dcp_idle_timeout - The maximum time a DCP connection can be idle before it
                       is disconnected.

    dcp_consumer_buffer_ratio - Ratio of the BucketQuota that can be allocated by
                                all DCP consumers for buffered messages

    dcp_backfill_byte_limit - Max bytes a Producer connection can backfill into its
                              streams readyQs (aggregate) before backfill is paused

  Available params for "set_vbucket_param":
    max_cas - Change the max_cas of a vbucket. The value and vbucket are specified as decimal
              integers. The new-value is interpretted as an unsigned 64-bit integer.

              cbepctl host:port -b default set_vbucket_param max_cas <vbucket-id> <new-value>

  Triggering compaction with "compact":
    4 parameters
      vbucket        - ID of the vbucket to compact
      purgeBeforeTs  - Purge deleted-items less than this UNIX epoch time-stamp.
                       Compaction will discard any delete with a delete-time
                       less than this value.
      purgeBeforeSeq - Purge deleted-items with a seqno less than this value.
                       This value is only used when non 0.
      dropDeletes    - When non-zero, any persisted deletes are discarded.

    Examples (all on vbucket 512):

      Drop all deletes: cbepctl host:port -b default compact 512 0 0 1
      Purge everything before 11/Dec/2021 14:11:25 : cbepctl host:port -b default compact 512 1639231885 0 0
      Purge everything before seqno 1000 : cbepctl host:port -b default compact 512 0 1000 0

    """)

    c.addCommand('drain', drain, "drain")
    c.addCommand('set', set_param, 'set type param value')
    c.addCommand('set_vbucket_param', set_vbucket_param, 'type vbucket value')
    c.addCommand('start', start, 'start')
    c.addCommand('stop', stop, 'stop')
    c.addCommand(
        'compact',
        run_compaction,
        'vbucket purgeBeforeTs purgeBeforeSeq dropDeletes')

    # Add collection get manifest methods - These should not be executed
    # except for when required by support/development
    c.addCommand('set_collections', set_collections, None, hidden=True)
    c.addCommand('get_collections', get_collections, None, hidden=True)

    # Add flag to allow setting parameters that should not be set via cbepctl
    c.addHiddenFlag('--force', 'force')

    c.execute()
