"""PatchRunner integration hook for Content Library

This module integrates Content Library patching scripts with
PatchRunner Framework.
    Ref: https://wiki.eng.vmware.com/VSphere2016/vSphere2016Upgrade/Inplace/Patch_Extensibility

This framework is only supported for vCSA. Even though this framework supports state changes,
as of now, components are not allowed to make any state or database changes, due to lack of support
for this framework for Windows VC.
    Ref: https://wiki.eng.vmware.com/ContentLibrary/2016/BuildToBuildPatching#Resolved

Note:
    When a new Content Library B2B Patch Version is added,
    content-library-firstboot.INSTALL_VERSION needs to be updated.

"""

COMPONENT_NAME = "content-library"

try:
    # Setup python path for Content Library Modules
    import os
    import sys
    import traceback
    sys.path.append(os.path.dirname(os.path.abspath(__file__)))
except:
    # Should not happen, Workaround for PEP8 E402
    sys.exit("Most likely ImportError: %s" % traceback.format_exception(*sys.exc_info()))

import logging

from base import PatchStepExecutor, PatchStepVerifier, PatchVersion
from db_patch import DBPatchTask
from extensions import extend, Hook
from patch_specs import (DiscoveryResult,
                         PatchInfo,
                         Requirements,
                         RequirementsResult,
                         ValidationResult)
from patches import cls66
from utils import (get_latest_patch_executor,
                   get_source_version,
                   set_source_version,
                   load_patchers,
                   apply_patches)
import vcsa_utils

__author__ = 'VMware, Inc.'
__copyright__ = 'Copyright 2016-2017 VMware, Inc. All rights reserved.'


logger = logging.getLogger(__name__)

# Following tuple() should include the modules containing patch-logic
MODULES_TO_SCAN_FOR = (cls66, )


@extend(Hook.Discovery)
def discover(patch_context):
    """Decides if the component should participate in the patching process.

    Components can make this decision based on various deployment types and
    applicability of the patch.

    Args:
        patch_context (patch_specs.PatchContext): Patch Context provided by the PatchRunner
            patch_context provides user-input in a dict, locale, and working directory

    Returns:
        DiscoveryResult

    """
    logger.info("Executing Discovery Hook for Content Library")
    return vcsa_utils.getComponentDiscoveryResult(COMPONENT_NAME)


@extend(Hook.Requirements)
def collect_requirements(patch_context):
    """Checks for any component-specific patching requirements, and if needed
    asks users for input.

    A component may need some user-input (eg. username, password for sso), or it may
    have some system specific requirements (eg. storage space, certain preconditions).
    In this hook, the component should make sure that all the requirements necessary
    for the patch to succeed are satisfied. Any requirement which is not satisfied,
    should be returned via RequirementsResult to the user.

    TODO bagatea: Add a way for an individual patch to specify requirements, if needed.

    Args:
        patch_context (patch_specs.PatchContext): Patch Context provided by the PatchRunner

    Returns:
        RequirementsResult

    """
    logger.info("Executing Requirements Hook for Content Library")

    mismatches = []
    requirements = Requirements()
    patch_executor = get_latest_patch_executor(MODULES_TO_SCAN_FOR)
    if patch_executor:
        patch_info = PatchInfo(summary=patch_executor.version.summary)
    else:
        patch_info = PatchInfo()
    # No specific requirement to check here
    return RequirementsResult(requirements, patch_info, mismatches)


@extend(Hook.Validation)
def validate(patch_context):
    """Validates the user-input and checks that it satisfied component requirements.

    In this hook, a component validates any input provided by the user as a result of
    any requirement mismatches detected in Hook.Requirements.
    This method returns ValidationResult containing list of validation mismatches if any.
    Until the list of mismatches is empty, the PatchRunner framework does not proceed
    with the patching process.

    Args:
        patch_context (patch_specs.PatchContext): Patch Context provided by the PatchRunner

    Returns:
        ValidationResult
    """
    mismatches = []
    logger.info("Executing Validation Hook for Content Library")
    # We have no requirements to validate here
    return ValidationResult(mismatches)


@extend(Hook.Prepatch)
def execute_prepatch_actions(patch_context):
    """Runs component specific actions which need to be performed before
    installing the rpms for the components and its dependencies.

    In this hook, components can save state before the patch package is
    installed. After the execution of this hook, required services in the
    vCSA are stopped and the rpms are installed.

    Note: Rollback from failure is not the purpose of this hook.
    Customers are advised to take snapshot of the vCSA and databases,
    before starting the patching process.

    Args:
        patch_context (patch_specs.PatchContext): Patch Context provided by the PatchRunner

    Returns:
        None
    """
    pass


@extend(Hook.Patch)
def execute_patch(patch_context):
    """Executes patching scripts to reconfigure the component state, if
    needed.

    Note: This hook is invoked after the rpms are installed.

    Args:
        patch_context (patch_specs.PatchContext): Patch Context provided by the PatchRunner

    Returns:
        None
    """
    logger.info("Executing Patch Hook for Content Library")
    # Perform database patches, DB patch task will update DB schema from current DB schema
    # version to the latest DB schema from the patch accordingly
    logger.info("Patching DB changes for Content Library")
    dbPatchTask = DBPatchTask(COMPONENT_NAME)
    dbPatchTask.run()

    # Perform State Changes
    source_version = get_source_version()
    if source_version:
        logger.info("Patching state changes for Content Library from version: %s",
                    source_version.version)
        patch_executors, patch_verifiers = load_patchers(MODULES_TO_SCAN_FOR)
        apply_patches(patch_context, patch_executors, patch_verifiers, source_version)


@extend(Hook.OnSuccess)
def execute_on_success(patch_context):
    """Perform cleanup and post patch configuration changes if needed

    Args:
        patch_context (patch_specs.PatchContext): Patch Context provided by the PatchRunner

    Returns:
        None
    """
    patch_executor = get_latest_patch_executor(MODULES_TO_SCAN_FOR)
    logger.info("Content library has been successfully patched to version: %s",
                patch_executor.version.version)
