# Copyright 2015 VMware, Inc.  All rights reserved. -- VMware Confidential
#
'''
Script responsible to setup environment and execute the validation hook of vCSA
components. The script calls only those components which have been discovered
in prior discovery phase.
'''

import logging
import json

from extensions import Hook
from patch_specs import Mismatch, ValidationResult
from status_reporting_sdk.json_utils import ObjectJsonConverter, prettyJsonDump

from vmware.patching.executor.execution_facade import executeComponentHook
from vmware.patching.data.model import PatchPhaseContext
from vmware.patching.utils.reporting_utils import INTERNAL_ERROR_TEXT, INTERNAL_ERROR_RESOLUTION, \
    configureLocalization

logger = logging.getLogger(__name__)

def _isSuccessful(validationResults):
    '''Test if validation component hook completed successfully.

    @param validationResult: Lookup of component name cross its ValidationResult
    @type validationResult: dict of str X patch_specs.ValidationResult

    @return: False if any component report an error mismatch, otherwise True
    @rtype: bool
    '''
    for compName, validationResult in validationResults.items():
        if validationResult is None:
            logger.warning("Component %s did not extend validation hook", compName)
            continue

        # Iterate over component mismatches
        for mismatch in validationResult.mismatches:
            if mismatch.severity == Mismatch.ERROR:
                return False

    return True

def _validateComponents(ctx, userData):
    '''Executes the components Validate hook.

    @param ctx: Global CPO patch phase context.
    @type ctx: vmware.patching.data.model.PatchPhaseContext

    @param userData: Customer input, as result of component questions raised
      in @Discovery patching hook.
    @type userData: dict

    @return: Lookup of component name cross its ValidationResult
    @rtype dict of str X patch_specs.ValidationResult

    @raise Exception: if validation hook execution fails.
    '''
    result = {}

    for c in ctx.getPatchableComponents(sort=False):
        logger.info("Validate component %s", c.name)

        validationResult = executeComponentHook(Hook.Validation, ctx, c, userData, None,
                                                expectedResultType=(type(None), ValidationResult))
        result[c.name] = validationResult

    return result

def _reportResults(validationResults, outputFile):
    '''Report validate results into the file.

    @param validationResults: Lookup of component name cross its ValidationResult
    @type validationResults: dict of str X patch_specs.ValidationResult

    @param outputFile: Output file
    @type outputFile: str
    '''
    # TODO iradev: Consider using real software-update specs rather than
    # serializing to dictionary. This would handle eventual property mismatches
    if not outputFile:
        logger.warning("The output file is not provided. Skip writing results")
        return

    mismatches = []
    for validationResult in validationResults.values():
        if validationResult is None:
            continue

        mismatches.extend(validationResult.mismatches)

    prettyJsonDump(outputFile, {"mismatches" : mismatches})

def validate(stageDir, userData, outputFile):
    '''The entry point of validate CPO phase.

    The validate phase is responsible to setup environment and execute the
    validate hook of vCSA components. The validation hook is executed only
    on those components which have been discovered in prior discovery phase.

    @param stageDir: global CPO stage directory. All components stage
      directories will be created as sub-directories of global stage directory.
    @type stageDir: str

    @param userData: Customer input, as result of component questions raised
      in @Discovery patching hook.
    @type userData: dict

    @param outputFile: File where the output of the command needs to be written
      to
    @type outputFile: str

    @return: True if the validation phase succeed and False otherwise
    @rtype: bool

    @raise Exception: if validation phase raise unhandled exception
    '''
    try:
        ctx = PatchPhaseContext.load(stageDir)
        configureLocalization(ctx.locale)
        validationResults = _validateComponents(ctx, userData)

        # Log the validation results
        logger.info("Validation completed. Result: %s\n",
                    json.dumps(validationResults,
                               default=ObjectJsonConverter(False).encode,
                               indent=4,
                               sort_keys=True))
        success = _isSuccessful(validationResults)
        return success
    except Exception:
        # In case of exception, generate default error
        mismatch = Mismatch(text=INTERNAL_ERROR_TEXT,
                            description=INTERNAL_ERROR_TEXT,
                            resolution=INTERNAL_ERROR_RESOLUTION)
        validationResult = ValidationResult(mismatches=[mismatch])
        validationResults = {"default_errror_handler" : validationResult}
        raise
    finally:
        _reportResults(validationResults, outputFile)