# Redpoint Global (c) - 2024-01-04

# Migration Script for all files EXCEPT Realtime Layouts

# Prerequisit install of libraries
#py -m pip install --upgrade pip
#py -m pip install colorama
#py -m pip install requests
#py -m pip install pywinauto


# Libraries


import time
# File / Path maintenance
import inspect
import os
# Maintenance / Display of JSON
import json
# Config of INI files
import configparser
config_data = configparser.ConfigParser()
# Send Requests to HTTP
import requests
# Run Shell commands
import subprocess
# mimic keystrokes for file copy utility
from pywinauto.application import Application
from pywinauto.keyboard import send_keys
# handle INI file path
from pathlib import Path

# Pretty-fy the output
from colorama import just_fix_windows_console , Fore

# Datetime maintenance
from datetime import datetime
just_fix_windows_console()



def errorStatus(status_code,message):
    if status_code != 200:
        print()
        print()
        print(message + ' Failed')
        exit()
    else:
        print()
        print()
        print(message + ' Succeeded')

def buildInI():
    # Setup INI file
    print(Fore.GREEN + "Connecting to INI file" + Fore.RESET)
    # Setup the ini Files
    source_file_path = inspect.getfile(inspect.currentframe())
    scrPath = os.path.dirname(source_file_path)
    scrName = os.path.basename(source_file_path)
    iniFile=Path(os.path.splitext(source_file_path)[0]+'.ini')
    iniNew = False
    if os.path.isfile(iniFile):
        print(" INI file exists")
        config_data.read(iniFile)
    else:
        print(" new INI file")
        iniNew = True

    if config_data.has_section('server') is False:
        print(" Adding Section:server")
        config_data.add_section('server')
        iniNew = True

    if config_data.has_section('credentials') is False:
        print(" Adding Section:credentials")
        config_data.add_section('credentials')
        iniNew = True

    if config_data.has_section('utility') is False:
        print(" Adding Section:utility")
        config_data.add_section('utility')
        iniNew = True

    if config_data.has_section('targetAPI') is False:
        print(" Adding Section:targetAPI")
        config_data.add_section('targetAPI')
        iniNew = True

    if config_data.has_option('server','sourceUrlBase') is False:
        print(" Adding Option:sourceUrlBase")
        config_data.set('server','sourceUrlBase','sourceUrlBase')
        iniNew = True

    if config_data.has_option('server','sourceClientId') is False:
        print(" Adding Option:sourceClientId")
        config_data.set('server','sourceClientId','sourceClientId')
        iniNew = True

    if config_data.has_option('server','sourceClientSecret') is False:
        print(" Adding Option:sourceClientSecret")
        config_data.set('server','sourceClientSecret','sourceClientSecret')
        iniNew = True

    if config_data.has_option('credentials','authMethod') is False:
        print(" Adding Option:authMethod")
        config_data.set('credentials','authMethod','default_authMethod')
        iniNew = True

    if config_data.has_option('credentials','authUser') is False:
        print(" Adding Option:authUser")
        config_data.set('credentials','authUser','default_authUser')
        iniNew = True

    if config_data.has_option('credentials','authPass') is False:
        print(" Adding Option:authPass")
        config_data.set('credentials','authPass','default_authPass')
        iniNew = True

    if config_data.has_option('utility','fileUtilityPath') is False:
        print(" Adding Option:fileUtilityPath")
        config_data.set('utility','fileUtilityPath','default_fileUtilityPath')
        iniNew = True

    if config_data.has_option('targetAPI','targetUrlBase') is False:
        print(" Adding Option:targetUrlBase")
        config_data.set('targetAPI','targetUrlBase','targetUrlBase')
        iniNew = True

    if config_data.has_option('targetAPI','targetClientID') is False:
        print(" Adding Option:targetClientID")
        config_data.set('targetAPI','targetClientID','targetClientID')
        iniNew = True
    sourceUrlBase                =config_data.get('server','sourceUrlBase')
    sourceClientId                =config_data.get('server','sourceClientId')
    sourceClientSecret            =config_data.get('server','sourceClientSecret')
    authMethod              =config_data.get('credentials','authMethod')
    authUser                =config_data.get('credentials','authUser')
    authPass                =config_data.get('credentials','authPass')
    fileUtilityPath         =config_data.get('utility','fileUtilityPath')
    targetUrlBase         =config_data.get('targetAPI','targetUrlBase')
    targetClientID         =config_data.get('targetAPI','targetClientID')

    print(Fore.GREEN + "INI Parameters" + Fore.RESET)
    print('   sourceUrlBase              : '   + sourceUrlBase)
    print('   sourceClientId              : '   + sourceClientId)
    print('   sourceClientSecret          : '   + sourceClientSecret)
    print('   authMethod            : '   + authMethod)
    print('   authUser              : '   + authUser)
    print('   fileUtilityPath       : '   + fileUtilityPath)
    print('   targetUrlBase        : '   + targetUrlBase)
    print('   targetClientID          : '   + targetClientID)
    print('-------------------------------------------------------------')

    iniFile=Path(os.path.splitext(source_file_path)[0]+'.ini')
    if  iniNew == True:
        config_data.write( iniFile.open("w"))
        print("Saving INI file")
        print()
        print(Fore.RED + "Edit the INI file to check urlBases and credentials before running again" + Fore.RESET)
        exit()
    return sourceUrlBase, sourceClientId, sourceClientSecret,authMethod ,authUser,authPass ,fileUtilityPath,targetUrlBase,targetClientID

def getAuthToken(clientSecret,authUser,authPass,urlBase,debug=False):
    # =============================================
    # =============================================
    # Send Request for Authentication
    print("=======================================")
    print(Fore.GREEN + "Authentication" + Fore.RESET)

    url = urlBase + '/token'
    jsonAuthenticationHeader = {
        "accept": "application/json" ,
        "Content-Type": "application/json"
        }
    jsonAuthenticationData = {
        'client_id'     : 'rpiwebapi',
        'client_secret' : clientSecret,
        'grant_type'    : 'password',
        'username'      : authUser,
        'password'      : authPass,
        }
    Response = requests.get(url , headers = jsonAuthenticationHeader , data = jsonAuthenticationData)
    jsonResponse = Response.json()
    if (debug):
        print(Fore.GREEN + "URL" + Fore.RESET)
        print(Fore.RED + url + Fore.RESET)
        print(Fore.GREEN + "Request Header" + Fore.RESET)
        print(Fore.RED + json.dumps(jsonAuthenticationHeader, indent=2) + Fore.RESET)
        print(Fore.GREEN + "Request Data" + Fore.RESET)
        print(Fore.RED + json.dumps(jsonAuthenticationData, indent=2) + Fore.RESET)
        print("jsonResponse Status Code : "+ str(Response.status_code))
        print("jsonResponse Content (decoded)")
        print(Fore.YELLOW + json.dumps(jsonResponse , indent=2) + Fore.RESET)

    errorStatus(Response.status_code,"Authentication")
    return jsonResponse['access_token']
    

def getAuthedRequestHeader(clientID,authToken,debug=False):
    # Now authenticated these are the header items we need to use to run the additional requests
    # Build Header for remaining calls
    jsonRequestHeader = {
        'accept': 'application/json' ,
        'X-ClientID': '' + clientID + '',
        'Content-Type': 'application/json',
        'authorization': 'Bearer ' + authToken + ''
        }
    if (debug):
        print()
        print(Fore.GREEN + "Primary Request Header (for all future requests)" + Fore.RESET)
        print(Fore.RED + json.dumps(jsonRequestHeader, indent=2) + Fore.RESET)
    return jsonRequestHeader


def getSearchString(): 
    searchString=input('Enter the searchstring: ')
    if searchString=="":
        searchString="*"
    return searchString

def exportFileInfo(urlBase,searchString,jsonRequestHeader,debug=False,pageSize=5):
    # =============================================
    # =============================================
    # Export File Infos
    print("=======================================")
    print(Fore.GREEN + "File Infos" + Fore.RESET)
    url = urlBase + '/api/v1/client/file-system/search-file-infos'

    jsonRequestData = {'searchString':searchString,
                        "pageSize":pageSize}
    if (debug):
        print(Fore.GREEN + "URL" + Fore.RESET)
        print(Fore.RED + url + Fore.RESET)
        print(Fore.GREEN + "Request Data" + Fore.RESET)
        print(Fore.RED + json.dumps(jsonRequestData, indent=2) + Fore.RESET)
    Response = requests.post(url , headers = jsonRequestHeader , json = jsonRequestData)
    
    errorStatus(Response.status_code,"File Infos Download")
    return Response.json()

def parseFileInfos(fileInfoResponse,urlBase,debug=False):
    # =============================================
    # =============================================
    # Parse File Infos
    print("=======================================")
    print(Fore.GREEN + "Parsing File Infos" + Fore.RESET)
    objectDict={}
    folderDict={}
    smartAssetArray=[]
    for i in range(len(fileInfoResponse["results"])):
        result=fileInfoResponse["results"][i]
        id=result["id"]
        url=urlBase+f"/api/v1/client/file-system/file-info?message.id={id}"
        Response=requests.get(url,headers=jsonRequestHeader)
        if (debug):
            print(url)
            print(str(Response))
        fileJson=Response.json()
        path=''
        for j in range(len(fileJson["fullPath"].split('\\'))-1):
            word=fileJson["fullPath"].split('\\')[j]
            if path=='':
                path=word
            else:
                path=path+"\\"+word
        if path in folderDict:
            folderDict[path]+=";"+result["id"]
        else:
            folderDict[path]=result["id"]
        data={
            "id":result["id"],
            "path":path,
            "name":result["name"]
        }
        if result["typeName"]=="Smart Asset":
            smartAssetArray.append(result["id"])
        objectDict[result["id"]]=data
    if (debug):
        print(Fore.GREEN + "objectDict" + Fore.RESET)
        print(json.dumps(objectDict))
        print(Fore.GREEN + "folderDict" + Fore.RESET)
        print(json.dumps(folderDict))
        print(Fore.GREEN + "smartAssetArray" + Fore.RESET)
        print(smartAssetArray)
    
    print(Fore.GREEN + "File Infos Parsing Complete" + Fore.RESET)
    return objectDict,folderDict,smartAssetArray

def getFolderIdFromPath(folderPath,urlBase,debug=False): 
    print(folderPath)
    folderPathUrl=urlBase+"/api/v1/client/file-system/folder/by-fullpath?message.fullPath="+folderPath.replace('\\\\',"\\")
    Response=requests.get(folderPathUrl,headers=jsonRequestHeader)
    folderPathJson=Response.json()
    if (debug):
        print(Fore.GREEN + "folderPathUrl" + Fore.RESET)
        print(folderPathUrl)
        print(Fore.GREEN + "folderPathResults" + Fore.RESET)
        print(json.dumps(folderPathJson))
    return folderPathJson["id"]

def parseFolderContents(folderId,urlBase,debug=False,searchString=None):
    # Logic intended to handle case where there are 
    # multiple files of same name but different types in one folder.
    # This will result in them needing to be sorted by the SQL ID
    fileNameArray=[]
    fileNameDict={}
    layoutArray=[]
    folderContentUrl=urlBase+f"/api/v1/client/file-system/folders/content?message.id={folderId}"
    Response=requests.get(folderContentUrl,headers=jsonRequestHeader)
    folderContentJson=Response.json()
    if (debug):
        print(Fore.GREEN + "folderContentURL" + Fore.RESET)
        print(folderContentUrl)
        print(Fore.GREEN + "folderContentResults" + Fore.RESET)
        print(json.dumps(folderContentJson))
    
    for file in folderContentJson["fileStorageItems"]:
            if searchString is not None:
                if searchString in file["description"]:
                    layoutArray.append(file["id"])
            if file["typeName"]!="Folder":
                name=file["name"]
                #build fileNameArray
                if name not in fileNameArray:
                    fileNameArray.append(name)
                #build fileNameDict
                if name in fileNameDict:
                    fileNameDict[name].append(file["id"])
                else:
                    fileNameDict[name]=[file["id"]]
    fileNameArray=sorted(fileNameArray, key=str.casefold)
    if (debug):
            print(Fore.GREEN + "fileNameArray" + Fore.RESET)
            print(fileNameArray)
            print(Fore.GREEN + "fileNameDict" + Fore.RESET)
            print(fileNameDict)
    return fileNameArray,fileNameDict,layoutArray

def buildCommandsfromPath(folderPath,urlBase,objectDict,debug=False,searchString=None):
    commandArray=[]
    #Use Folderpath to find folderId
    folderId=getFolderIdFromPath(folderPath,urlBase,debug)

    #use folderId to get folder contents
    fileNameArray,fileNameDict,layoutArray=parseFolderContents(folderId,urlBase,debug,searchString)
    if len(layoutArray)>0:
        objectDict=layoutArray
    directoryIndex=0
    for fileName in fileNameArray:
        for fileId in fileNameDict[fileName]:
            directoryIndex+=1
            if fileId not in objectDict: 
                continue
            command=f" \"cd \{folderPath}\" \"dir\" \"copy {directoryIndex}\" \"exit\""
            commandArray.append(command)
    return commandArray,layoutArray

def buildCommandArray(urlBase,folderDict,objectDict,debug=False):
    print("=======================================")
    print(Fore.GREEN + "Building Command Array" + Fore.RESET)
    commandArray=[]
    for key in folderDict:
        newCommands=buildCommandsfromPath(key,urlBase,objectDict,debug)
        commandArray+=newCommands[0]
        
    if (debug):
        print(Fore.GREEN + "commandArray " + Fore.RESET)
        print(commandArray)
    return commandArray

def processCommands(commandArray,fileUtilityPath,debug=False):
    for command in commandArray:
        if (debug): 
            print(fileUtilityPath+command)
        fullCmd=fileUtilityPath+command
        subprocess.Popen(fullCmd,encoding = 'UTF-8',creationflags=subprocess.CREATE_NEW_CONSOLE)
        Application().connect(path=fileUtilityPath)
        time.sleep(1)
        try: 
            send_keys("o")
        except:
            print("File not updated")
        time.sleep(2)


def publishSmartAssets(smartAssetArray,urlBase,debug=False):
    for asset in smartAssetArray:
        url=urlBase+"/api/v1/client/jobs/start/publish-smart-asset"
        if (debug):
            print(asset)
            print(url)
        jsonRequestData={
            "id":asset,
            "publishOptions":{}
        }
        Response=requests.post(url,headers=jsonRequestHeader, json=jsonRequestData)
        message='Publishing Smart Asset ' +  asset
        errorStatus(Response.status_code,message)
        if (debug):
            print(json.dumps(Response.json()))

#initialize
debug=True
pageSize=50
sourceUrlBase, sourceClientId, sourceClientSecret,authMethod ,authUser,authPass ,fileUtilityPath,targetUrlBase,targetClientID=buildInI()

#Authorization
authToken=getAuthToken(sourceClientSecret,authUser,authPass,sourceUrlBase,debug)
jsonRequestHeader=getAuthedRequestHeader(sourceClientId,authToken,debug)

#Search Definition
searchString=getSearchString()
#***ADD CUSTOM FORMATTING HERE***

# Asset Collection
fileInfoResponse=exportFileInfo(sourceUrlBase,searchString,jsonRequestHeader,debug,pageSize)
objectDict,folderDict,smartAssetArray=parseFileInfos(fileInfoResponse,sourceUrlBase,debug)
commandArray=buildCommandArray(sourceUrlBase,folderDict,objectDict,debug)

#add RealtimeLayouts
layoutCommands,layoutArray=buildCommandsfromPath('Configuration Collections\Configuration Collections',sourceUrlBase,{},debug=debug,searchString=searchString)
commandArray+=layoutCommands
#migrate to target environment
processCommands(commandArray,fileUtilityPath,debug)

# publishing in target environment
targetAuthToken=getAuthToken(sourceClientSecret,authUser,authPass,targetUrlBase,debug)
targetRequestHeader=getAuthedRequestHeader(targetClientID,targetAuthToken,debug)
publishSmartAssets(smartAssetArray,targetUrlBase,debug)






