#!/usr/bin/env python
from __future__ import print_function

import fnmatch
import os
import sys
import argparse
import subprocess
import json
from collections import OrderedDict
from six import iteritems

import globals
import toolconfig
import multi_facility_process_helper_client
import upgradeRelease
from globals import UserParamsException

def getFacilityRelease4IOCs(facilityarg, searchioc, versionsAndReleases):
    '''Iterate thru versionsAndReleases and give us a list of facility/release tuples that tell us which facility IOCs are located and their current Releases'''
    facilityReleaseTuples = []
    for facility, vr in iteritems(versionsAndReleases):
        if not vr:
            continue
        if not vr[0]:
            continue
        for ioc in vr[0]['iocs']:
            if fnmatch.fnmatch(ioc['ioc'], searchioc):
                facilityReleaseTuples.append((facility, ioc['ioc'], ioc['release']))
    if not facilityReleaseTuples:
        raise UserParamsException("Cannot find IOC with name {0}. If you expected to see an IOC here, you can use cram validate to see if the softlinks are set up correctly".format(searchioc))
    elif len(facilityReleaseTuples) > 1:
        if (not facilityarg or facilityarg == 'ALL'):
            raise UserParamsException("Found more than one IOC with the same name " + searchioc + ". In this case we need an unambiguous facility argument to disambiguate the IOC.")
    return facilityReleaseTuples

def determineIOCFacilityCombos(name, type, facilityarg, iocarg, versionsAndReleases):
    '''Determine a list if tuples where the script needs to run based on the arguments'''
    iocFacilityCombos = []
    if type in ['SIOC', 'HIOC']:
        if not iocarg:
            raise UserParamsException("For IOC's, we need to specify the IOC's using the -i/--ioc argument. To run this script in all IOCS, use -i ALL. You can also specify one or more or ALL facilities. Specify more than once IOC using a command separated list of IOC names. For example, -i sioc-ms-001,sioc-ms-002.")
        if iocarg == 'ALL':
            if not facilityarg:
                raise UserParamsException("For ALL IOC's, you need to specify one/more/ALL facility(ies) using the -f/--facility argument.")
            facilities = facilityarg.split(",")
            if facilityarg == "ALL":
                facilities = toolconfig.getListOfFacilities()
            for facility in facilities:
                if not versionsAndReleases[facility][0]:
                    continue
                # MASTER must go first, or the "current" links are done wrong.
                iocFacilityCombos.append((facility, 'MASTER', versionsAndReleases[facility][0]['master']))
                for ioc in versionsAndReleases[facility][0]['iocs']:
                    (facility, iocName, release) = getFacilityRelease4IOCs(facility, ioc['ioc'], versionsAndReleases)[0]
                    iocFacilityCombos.append((facility, iocName, release))
        else:
            if facilityarg:
                if facilityarg != 'ALL':
                    # Filter versions and releases by facility
                    versionsAndReleases = {facilityarg: versionsAndReleases.get(facilityarg, [])}
            elif '*' in iocarg or '?' in iocarg:
                raise UserParamsException("When specifying IOCs using a wildcard, the facility must also be specified.")
            iocs = iocarg.split(",")
            for ioc in iocs:
                facilityReleaseTuples = getFacilityRelease4IOCs(facilityarg, ioc, versionsAndReleases)
                iocFacilityCombos.extend(facilityReleaseTuples)

            # Remove duplicates
            iocFacilityCombos = list(OrderedDict.fromkeys(iocFacilityCombos))
    else:
        if not facilityarg:
            raise UserParamsException("For HLA applications and PyDM displays, you need to specify one/more/ALL facility(ies). ")
        facilities = facilityarg.split(",")
        if facilityarg == "ALL":
            facilities = toolconfig.getListOfFacilities()
        for facility in facilities:
            iocFacilityCombos.append((facility, 'MASTER', versionsAndReleases[facility][0]))
    return iocFacilityCombos

def runScript(args):
    '''Run a script in the current release of one or more IOCs/HLA app in one or more sites/facilities'''
    packageInfo = toolconfig.determinePackageInfoFromArgs(args)
    name = packageInfo['name']
    type = packageInfo['type']
    script = args.script
    versionsAndReleases = upgradeRelease.determineExistingVersionsAndAvailableReleases(name, type)
    if globals.verbose:
        print("Versions and releases {0}".format(json.dumps(versionsAndReleases)))
    iocFacilityCombos = determineIOCFacilityCombos(name, type, args.facility, args.ioc, versionsAndReleases)
    for (facility, ioc, release) in iocFacilityCombos:
        if globals.verbose:
            print("Running {0} in {1} for {2} within {3}".format(script, facility, ioc, release))
        arg = {}
        arg['name'] = name
        arg['type'] = type
        arg['releaseFolder'] = toolconfig.determineReleaseFolder(facility, name, type)
        arg['linkFolder'] = toolconfig.determineLinkFolder(facility, name, type)
        arg['isMaster'] = ioc == 'MASTER'
        arg['facility'] = facility
        arg['ioc'] = ioc
        arg['release'] = release
        arg['script'] = script

        commandsAndArgs = []
        commandsAndArgs.append(('run', arg))
        results = multi_facility_process_helper_client.executeCommandsOnFacility(facility, commandsAndArgs)
        if results[0].get('retcode', -1) != 0:
            raise Exception("Running " + script + " for " + name +  " of type " +  type + "ioc/facilty" +  ioc +  facility +  " within " +  release + " failed")
        if 'stdout' in results[0]:
            print(results[0]['stdout'])
        if globals.verbose and 'stderr' in results[0]:
            print(results[0]['stderr'])


def execute(args):
    parser = argparse.ArgumentParser(prog="cram run", description="Run a script in the current release of one or more IOCs/HLA app in one or more sites/facilities.")
    parser.add_argument('-v', '--verbose', action='store_true', help='Enable verbose logging')
    parser.add_argument('-n', '--name', help='Specify the application name; we automatically pick this up from the package info if we are running this command in the folder containing the release')
    parser.add_argument('-t', '--type', help='Specifies the type of application from the deployment perspective. Possible values are ' + ", ".join(toolconfig.appTypes.keys()))
    parser.add_argument('-i', '--ioc', help='Runs the specified script only on the specified ioc(s). If this is ALL, we run the script on all IOCs in the facilities as identified by the facility argument.')
    parser.add_argument('-f', '--facility', help='Runs the specified script only on the specified facility(ies). If this is ALL, we run the script on all facilities. Possible values are - ALL, ' + ", ".join(toolconfig.getListOfFacilities()))
    parser.add_argument("script", help="The path to the script. In a fashion similar to the postDeploy and the preDeploy, provide a relative path from the release folder. For example, bin/myPostDeployScript will run a script called bin/myPostDeployScript in the current release folder. Note, the script needs to be executable.")
    args = parser.parse_args(args)
    globals.verbose = args.verbose

    try:
        runScript(args)
    except UserParamsException as upe:
        if not globals.verbose and not globals.isunittests:
            print(upe)
            sys.exit(-1)
        else:
            raise

if __name__ == "__main__":
    execute(sys.argv[1:])
