import json
from .SFAPI import Sobjects, query,restClient,jsonFile,utils,objectUtil,debugLogs,digitalCommerceUtil
import logging
import simplejson,sys
from operator import itemgetter
from inspect import getmembers, isfunction
import traceback
import colorama

varsFile = 'confIcli.json'
vars = None


def readVars(field=None):
    global vars
    vars = jsonFile.read(varsFile)

    if field != None:
        return vars[field]
    return vars

def setVar(name,value):

    vars = jsonFile.read(varsFile)
    vars[name]=value
    jsonFile.write(varsFile,vars)
    print(simplejson.dumps(vars, indent=4))

def getParam(argv,key,i=1):
    if key not in argv:
        return None

    ind = argv.index(key)
    if (ind+i <= (len(argv)-1)):
        arg = argv[ind+i]
        if arg[0] == '-':
            return None
        return arg
    return None

def listEnvs():
    cons = restClient.getConfigOrgs()
    cons = sorted(cons,key=itemgetter('name'))
    utils.printFormated(cons,fieldsString='name:isSandBox:instance_url:login.username:login.password:login.bearer')
    env = readVars('environment')
    print(f"Current Environment is {env}")

def _queryAndPrint(q,fields=None,systemFields=True,nullFields=False):

    res = query.queryRecords(q)
    if fields != None:
        res = utils.deleteNulls(res,systemFields,nullFields)
        if fields=='all':
            utils.printFormated(res)
        else:
            utils.printFormated(res,fields)
    else:
        res = utils.deleteNulls(res,systemFields,nullFields)
        print(simplejson.dumps(res, indent=4))
    
    print()
    print(f"Null values printed-> {nullFields}  systemFields printed--> {systemFields}")

def option_readme(args):
    if '-h' in args:
        return
    
    print(f"""
    {utils.CYELLOW}Significant updates:{utils.CEND}

     - Changes in version 0.49.0
       Added {utils.CYELLOW}-auto{utils.CEND} to the log parsing. This options activates the debug logs automatically for the -loguser especified. If no -loguser is specified it will do it for the user used to authenticate with the org.  
            {utils.CGREEN}InCli -logs -u DTI -tail -loguser username:onboarding@nosdti-parceiros.cs109.force.com -deletelogs -auto.{utils.CEND}
            This command will start listening for new logs for user onboarding@nosdti-parceiros.cs109.force.com in DTI and parsing them in real time. 
                -auto will activate the debug logs automatically, so the logs are collected without any configuration in Salesforce. 
                -delete will delete the logs from the server as they are parsed. A local copy for the 
            {utils.CGREEN}InCli -logs -u DTI -tail -deletelogs -auto.{utils.CEND}
            This command will start listening for new logs for the user used to authenticate with the org. -auto and -deletelogs as usual.

            * Replace DTI by the correct alias for the org in your sfdx setup. 

    {utils.CYELLOW}Parsed output fields:{utils.CEND}

     - {utils.CGREEN}time entry{utils.CEND}: The entry time to the event, such as METHOD_ENTRY in the debug log. Helpful to search for the line in the debug log
     - {utils.CGREEN}time exit{utils.CEND}:  The exit time for the event, such as METHOD_EXIT in the debug log. Helpful to search for the line in the debug log
     - {utils.CGREEN}ts{utils.CEND}: an esier to read time entry. Time in miliseconds. 
     - {utils.CGREEN}CPU{utils.CEND}: The Salesforce CPU Time. 
            This field is set when there is a CPU time event in the debug log. This are generated by the system and we have no control when they occur. 
            The parser will also look for DEBUG lines containing the CPU time information. The debug line needs to have the following format: "*** getCpuTime() *** Measured: CPUTime"
                Adding this APEX line in the code will increase the accuracy of CPU Time: {utils.CGREEN}System.debug('*** getCpuTime() *** Measured: ' + Limits.getCpuTime());{utils.CEND}
     - {utils.CGREEN}cpuD{utils.CEND}: The CPU time delta from the previous time the CPU Time was parsed. 
     - {utils.CGREEN}Qt{utils.CEND}: Total queries performed executing the APEX program (user apex + managed pacakes apex). This field accurate. 
     - {utils.CGREEN}Q{utils.CEND}: Total queries performed by the user APEX program. This field is almost accurate.
     - {utils.CGREEN}Qcm{utils.CEND}: Total queries performed by the managed package. We do not have control when they appear. When it changes is accurate.
     - {utils.CGREEN}Qe{utils.CEND}: Estimate queries performed by the managed package. This is an estimate for the manage package queries. Almost accurate. 
     - {utils.CGREEN}type{utils.CEND}: Type of log event.
     - {utils.CGREEN}line{utils.CEND}: the line in the APEX program. 
     - {utils.CGREEN}wait{utils.CEND}: time elapsed from the previous event. Typically is the time consumed by the APEX program or managed package between events.  
     - {utils.CGREEN}time{utils.CEND}: the time consumed executing the method. 
     - {utils.CGREEN}query{utils.CEND}: the total queries consumed bu the method. 
     - {utils.CGREEN}Call Stack{utils.CEND}:
            Some Call Stack string present a  "for: xRepetitions line_number". 
                - Repetitions: how many times the loop is repeated.
                - line_number: the position in the loop
                When there is a loop:
                    - The {utils.CGREEN}time{utils.CEND} field is the total time consumed by the method in the loop. 
                    - The {utils.CGREEN}wait{utils.CEND} field is the total wait time for the method in the loop. 
    
    Times are rounded to the closest milisecond. 
    """)

def option_q(args):
    if '-h' in args:
        help = f"""
        {utils.CYELLOW}-q "select..."{utils.CEND} query to execute. System Fields, such as LastModifiedDate, and null fields are not returned. 
            {utils.CYELLOW}-null{utils.CEND} - will print fields with null values
            {utils.CYELLOW}-system{utils.CEND} - will print the system fields
            {utils.CYELLOW}-all{utils.CEND} - will print both nulls and system fields
            {utils.CYELLOW}-fields "a:b:c:..."{utils.CEND} --> print in table format the specified fields.
            {utils.CYELLOW}-fields all{utils.CEND} --> print in table format"""
        return help

    #else:
    q = getParam(args,'-q')

    fields = getParam(args,'-fields') if '-fields' in args else None    
    nullFields = True if '-null' in args else False
    systemFields = True if '-system' in args else False
    all = True if '-all' in args else False
    if all == True:
        nullFields = True
        systemFields = True

    connectionInit(args)

    _queryAndPrint(q,fields=fields,systemFields=systemFields,nullFields=nullFields)

def option_cc(args):
    colorama.just_fix_windows_console()

    if '-h' in args:
        help = f"""
        {utils.CYELLOW}-cc{utils.CEND}       {utils.CGREEN}InCli -u "Org alias" -cc {utils.CEND}
            Checks the catalogs. Gets all catalogs, does a getOffers, getOfferDetails. Performs basketwithoutconfig and basket with config, if -basket is set. 
            {utils.CYELLOW}-code{utils.CEND} - Catalogue Code. does it for a single catalog. 
            {utils.CYELLOW}-list{utils.CEND} - List all the catalogues. 
            {utils.CYELLOW}-basket{utils.CEND} - Performs basket operations. """ 

        return help

    connectionInit(args)

   # path = getParam(args,'-checkCatalogs')
   # quantity = getParam(args,'-checkCatalogs',2)
    catcatalogueCode = getParam(args,'-code')
    list = True if '-list' in args else False
    basketOps = True if '-basket' in args else False

    account = getParam(args,'-account')

    if list == True:
        digitalCommerceUtil.printCatalogs()
        return

    digitalCommerceUtil.checkOffers(catcatalogueCode=catcatalogueCode,account=account,basketOps=basketOps)

    print()

def option_d(args):
    help = f"""
        {utils.CYELLOW}-d objectName{utils.CEND} --> Describe an object
            {utils.CYELLOW}-d objectName:fieldName{utils.CEND} --> describe a field in the object"""
    if '-h' in args:
        return help    

    connectionInit(args)

    objectField = getParam(args,'-d')
    if objectField == None:
        print(help)
        return

    ofs = objectField.split(':')

    sObjectName = ofs[0]
    fieldName = ofs[1] if len(ofs) > 1 else None

    res = Sobjects.describe(sObjectName)
    if fieldName == None:
        print(simplejson.dumps(res['fields'], indent=4))
    else:
        sibbling = objectUtil.getSiblingWhere(res['fields'],'name',fieldName)['object']

        print(simplejson.dumps(sibbling, indent=4))

def option_l(args):
    if '-h' in args:
        help = f"""
        {utils.CYELLOW}-l{utils.CEND} --> current org limits consuptions. """
        return help
    connectionInit(args)
    action = '/services/data/v51.0/limits'
    res = restClient.callAPI(action)
    records = []
    for key in res.keys():
        record = {
            'Limit':key,
            'Max':res[key]['Max'],
            'Remaining':res[key]['Remaining'],
        }
        record['Percent Remaining'] =  100 *(res[key]['Remaining']/res[key]['Max']) if res[key]['Max']>0 else 0
        record['__color__'] = ''

        if record['Max'] != 0:
            if record['Percent Remaining']<50:
                record['__color__'] = utils.CYELLOW
            if record['Percent Remaining']<25:
                record['__color__'] = utils.CYELLOW
        records.append(record)

    utils.printFormated(records)

def option_o(args):
    if '-h' in args:
        help = f"""
        {utils.CYELLOW}-o{utils.CEND} --> List all Objects
            {utils.CYELLOW}-name objectName{utils.CEND}  --> get one row from the object
            {utils.CYELLOW}-name objectName:Id{utils.CEND} --> get the row especified by the Id
            {utils.CYELLOW}-like name{utils.CEND} --> where the Name contains the substring name"""

        return help
        
    connectionInit(args)

    if '-name' in args:
        obj = getParam(args,'-name')
        chunks = obj.split(':')

        if len(chunks) == 1:
            q = f"select fields(all) from {chunks[0]} limit 1"
        else:
            q = f"select fields(all) from {chunks[0]} where Id='{chunks[1]}'"

        print('pre q:'+q)
        _queryAndPrint(q,systemFields=True,nullFields=True)
        return
        
    like = getParam(args,'-like') if '-like' in args else None    
    count = True if '-count' in args else False   

    objs = Sobjects.listObjects()
    if like is not None:
        if ':' not in like:
            like = f"name:{like}"        
        ls = like.split(':')
        outs = []
        for obj in objs:
      #      print(f"{str(ls[1])} . {str(obj[ls[0]])}")
            if str(ls[1]).lower() in str(obj[ls[0]]).lower():
                outs.append(obj) 
    else:
        outs = objs

    if count==True:
        for out in outs:
            if out['queryable'] == True:
                print("Quering objects row count.")
                print("", end=".")          
                try:
                    if out['name'] == 'AccountUserTerritory2View':
                        print()
                    c = query.query(f" select count(Id) from {out['name']}",raiseEx=True)
                    out['count'] = c['records'][0]['expr0']
                except Exception as e:
                    out['count'] = 'E'
            else:
                out['count'] = '-'
    
    #print(simplejson.dumps(objs, indent=4))
    utils.printFormated(outs,'name:label:associateParentEntity:associateParentEntity:queryable:count')

def option_default(args):
    help = f"""
        {utils.CYELLOW}-default:set key value{utils.CEND} --> defaults the specified key-value
        {utils.CYELLOW}-default:del key  {utils.CEND}  --> deletes the default
        {utils.CYELLOW}-default:get key  {utils.CEND}  --> displays the current value"""

    if '-h' in args:
        return help   

    if "-default:del" in args:
        key = getParam(args,'-default:del',1)
        if key == None:
            restClient.glog().info(f'Key not found in the provided arguments. {args}')
            return 
        restClient.delConfigVar(key)
        return

    if "-default:set" in args: 
        key = getParam(args,'-default:set',1)
        value = getParam(args,'-default:set',2)
        if key == None or value==None:
            print(help)
            return

        restClient.setConfigVar(key,value)
        print(f"Default value for {key} is {value}")

    if "-default:get" in args: 
        key = getParam(args,'-default:get',1)
        value =restClient.getConfigVar(key)
        print(f"Default value for {key} is {value}")

#def option_history(args):
#    return
#    if '-h' in args:
        help = """
        -history"""
        return help
#    print()
#    print('HISTORY:')
#    vars = jsonFile.read(varsFile)
#    for line in vars['history']:
        print(line)

def option_logs(args):
    help = f"""
        {utils.CYELLOW}-logs Id{utils.CEND} --> parse the log with the provided Id. No modifiers.
        {utils.CYELLOW}-logs inputfile {utils.CEND}--> parses the file provided in the inputfile. {utils.CGREEN}InCli -logs -inputfile "path to file/xxxx.log"{utils.CEND} No modifiers.
        {utils.CYELLOW}-logs store {utils.CEND}--> parses the files saved locally in order of creation(download) 
            {utils.CYELLOW}-error {utils.CEND}--> parses the files saved locally and displays only the ones with errors. {utils.CGREEN}InCli -logs -store -error "{utils.CEND} 
        {utils.CYELLOW}-logs -last X {utils.CEND}--> parses the last X logs. X as number.
        {utils.CYELLOW}-logs -tail {utils.CEND}--> activelly parsers new debug logs as they are created.
            {utils.CYELLOW}-deletelogs {utils.CEND}--> as log files are processed, they are deleted from the server. The local copy remains. if -loguser is not provided it will default to your User.
            {utils.CYELLOW}-auto {utils.CEND}--> automaticallyu configures the collection of logs for -loguser. If no -loguser provided defaults to your user. {utils.CBLINK}NEW{utils.CEND}
            {utils.CYELLOW}-loguser field:value{utils.CEND}, filter the logs for the specified user. The user can be specified by any field in the User Object. 
        {utils.CYELLOW}-logs{utils.CEND} --> lists log records in the org. 
            {utils.CYELLOW}-limit X{utils.CEND}, where X specifies the number of logs to list. Default 50 max 50K
                {utils.CGREEN}-logs -limit 1000{utils.CEND}, displays the last 1000 log records
            Modifiers:
            {utils.CYELLOW}-loguser field:value{utils.CEND}, filter the logs for the specified user. The user can be specified by any field in the User Object. 
                {utils.CGREEN}-loguser Id:0053O000000IHneQAG, -loguser "name:Onboarding Site Guest User", -loguser Alias:John.Doe, -loguser FirstName:Onboarding, -loguser ProfileId:00e3O000000IHneQAG{utils.CEND}
                {utils.CLIGHT_PURPLE}Defaultable loguser{utils.CEND}: Can be set to default. {utils.CGREEN}-default:set loguser "Alias:John.Doe{utils.CEND}"
            {utils.CYELLOW}-where X {utils.CEND}--> the where clause for a query on the ApexLog object.  
                {utils.CGREEN}InCli -logs -loguser Alias:John.Doe -where="Status<>'Success" -last 10" {utils.CEND} --> parses the last 10 logs for user John.Doe where the status is not Success
            {utils.CYELLOW}-file {utils.CEND}--> creates 2 files for the parsed file, txt and html format. 
            {utils.CYELLOW}-limitinfo {utils.CEND}--> prints the times when the limits (CPU, SOQL) are parsed.  """


    if '-h' in args:
        return help

    logId = getParam(args,'-logs')    
    last = getParam(args,'-last')  
    limit = getParam(args,'-limit')    
    limitInfo = True if '-limitinfo' in args else False   
    auto = True if '-auto' in args else False   

    level = getParam(args,'-level') 
    loguser = getParam(args,'-loguser') 
    whereClause = getParam(args,'-where') 
    toFile = True if '-file' in args else False
    inputfile = getParam(args,'-inputfile') 
    tail = True if '-tail' in args else False
    deletelogs = True if '-deletelogs' in args else False
    store = True if '-store' in args else False
    store_logId = getParam(args,'-store') 
    error = True if '-error' in args else False

    if loguser == None:
        loguser = restClient.getConfigVar('loguser')

    if logId == None and last== None and inputfile==None and tail == False and store==False:
        connectionInit(args)
        lim = 50 if limit == None else limit

        return debugLogs.printLogRecords(loguser=loguser,limit=lim,whereClause=whereClause)

    parseContext = {
        'logId' :logId,
        'filepath':inputfile,
        'printLimits':limitInfo,
        'lastN':last,
        'loguser':loguser,
        'level':level,
        'whereClause':whereClause,
        'writeToFile':toFile,
        'tail':tail,
        'deleteLogs':deletelogs,
        'parseStore':store,
        'logToStore':True,
        'print_only_errors':error,
        'operation':None,
        'auto':auto,
        'store_logId':store_logId
    }

    if tail:
        connection = connectionInit(args)
        parseContext['connection'] = connection
        return debugLogs.do_parse_tail(parseContext)

    elif store:
        return debugLogs.do_parse_storage(parseContext)

    elif inputfile != None:
        return debugLogs.do_parse_from_file(parseContext)

    elif logId != None:  #check if already downloaded        
        connectionInit(args)
        return debugLogs.do_parse_logId(parseContext)
    else:
        connectionInit(args)
        return debugLogs.do_parse_logs_lastN(parseContext)

def option_h(args):

    module = __import__('InCli')

    funcs = getmembers(sys.modules[__name__], isfunction)
    functions = [func[0] for func in funcs if func[0].startswith('option_')]

    for f in functions:
        if f == 'option_h':      continue
        args = '-h'
        p = eval(f'{f}(args)')
        if p != None: print(p)

def connectionInit(argsIn):

    userName_or_ofgAlias = getParam(argsIn,'-u') 
    return restClient.init(userName_or_ofgAlias)

def checkVersion():
    try:
        from importlib import metadata
    except ImportError:
        import importlib_metadata as metadata

    fileversion = metadata.version("InCli")

    try:
        restClient.init('ConnectionLess')
        restClient.glog().info("Checking version")
        res = restClient.requestRaw(url='https://pypi.org/pypi/InCli/json')
        version = res['urls'][1]['filename'].split('-')[1].split('.tar')[0]

        if version != fileversion:
            print()
            print(f"Version {fileversion}. {colorama.Fore.WHITE+colorama.Back.RED}Version {version} is available --> pip install InCli --upgrade{utils.CEND}")
        else:
            print()
            print(f"InCli version {fileversion} ")
    except Exception as e:
        print(e)

def _main(argsIn):

    if "-debug" in argsIn:    restClient.setLoggingLevel(level=logging.DEBUG)
    else:  restClient.setLoggingLevel(logging.INFO)

    restClient.glog().debug("In debug mode")
    checkVersion()

    if '-h' in argsIn:
        colorama.just_fix_windows_console()

        help = f"""
        {utils.CYELLOW}-readme{utils.CEND} --> new functionality added and some example. 

        {utils.CYELLOW}-u{utils.CEND} --> username or org alias to be used to log into the org. 
            {utils.CLIGHT_PURPLE}Defaultable u{utils.CEND}: Can be set to a default value --> {utils.CGREEN}InCli -default:set u "xx@adas.com {utils.CEND} """
        print(help)

    funcs = getmembers(sys.modules[__name__], isfunction)
    functions = [func[0] for func in funcs if func[0].startswith('option_')]

    args = []
    for argv in argsIn:
        if argv == '|':     break
        args.append(argv)

    for arg in args:
        ar = arg.split(':')[0][1:]
        ar = f"option_{ar}"
        if ar in functions:
            res = None
            res = eval(f'{ar}(args)')
            if '-h' not in argsIn:
                return res

    if '-h' in argsIn:
        print()
        print(utils.CBOLD+"SFDX Commands:"+utils.CEND)
        print(f" - {utils.CYELLOW}sfdx force:org:list --verbose --all {utils.CEND}--> to list all authorized Orgs and Connection Status")
        print(f" - {utils.CYELLOW}sfdx auth:web:login -r 'Instance Url' -a 'Alias' {utils.CEND}--> to re-authorize")
        print(f" - {utils.CYELLOW}sfdx auth:web:login -u 'userName'  -a 'alias' {utils.CEND}--> to authorize and Org")
        print()

def main():
    argsIn = sys.argv

    try:
        _main(argsIn)

    except KeyboardInterrupt as e:
        print()
        print("ate ja")
    except Exception as e:
        utils.printException(e)
       # print(traceback.format_exc())

        if "-debug" in sys.argv:
            print(traceback.format_exc())

if __name__ == '__main__':
    main()

