import pandas as pnd
import cobra

from .manual import get_deprecated_kos
from .manual import get_rids_with_mancheck_gpr
from .manual import get_rids_with_mancheck_balancing



def check_gpr(logger, rid, row, kr_ids, idcollection_dict, addtype, outdir): 
    
    
    # define the itemtype:
    if addtype=='R':
        itemtype = 'Reaction' 
    else: itemtype = 'Transporter'
    
    
    # check presence of the GPR
    if pnd.isna(row['gpr_manual']): 
        logger.error(f"{itemtype} '{rid}' has missing GPR: '{row['gpr_manual']}'.")
        return 1
    
    
    # get ko_ids in this reaction:
    ko_ids_parsed = row['gpr_manual'].strip()
    ko_ids_parsed = ko_ids_parsed.replace(' and ', ',')
    ko_ids_parsed = ko_ids_parsed.replace(' or ', ',')
    ko_ids_parsed = ko_ids_parsed.replace('(', '')
    ko_ids_parsed = ko_ids_parsed.replace(')', '')
    ko_ids_parsed = ko_ids_parsed.split(',')
    
    
    # collect all the ko_ids for this reaction:
    if addtype=='R':
        ko_for_rid = set()   
        for kr_id in kr_ids:
            if kr_id == 'RXXXXX': 
                continue
            ko_for_rid = ko_for_rid.union(idcollection_dict['kr_to_kos'][kr_id])
            
    
    # check if these ko_ids exist:
    for ko_id in ko_ids_parsed:
        ko_id = ko_id.strip() 
        
        if ko_id in get_deprecated_kos():
            pass
        elif ko_id not in idcollection_dict['ko'] and ko_id != 'spontaneous' and ko_id != 'orphan':
            logger.error(f"{itemtype} '{rid}' has an invalid KEGG Ortholog: '{ko_id}'.")
            return 1   # can be commented when migrating to new kegg release
        
        
        # check if these ko_ids are really assigned to this reaction:
        if addtype=='R':
            if ko_id not in ko_for_rid and ko_id != 'spontaneous' and ko_id != 'orphan':
                if kr_id != 'RXXXXX':
                    if rid not in get_rids_with_mancheck_gpr():
                        with open(f"{outdir}/logs/R.orthlink.txt", 'a') as f:
                            print(f"Ortholog '{ko_id}' should not be linked to reaction '{rid}' (available for {kr_ids}: {ko_for_rid}).", file=f)
            
    
    # check if some ko_ids are missing from this reaction:
    if addtype=='R':
        missing_ko_ids = ko_for_rid - (set(ko_ids_parsed) - set(['spontaneous', 'orphan']))
        if len(missing_ko_ids) > 0:
            logger.error(f"Orthologs {missing_ko_ids} are missing from reaction '{rid}' ({kr_ids}).")
            return 1   # can be commented when migrating to new kegg release
            
            
    return 0
                


def check_rstring_arrow(logger, rid, row, addtype='R'):
    
    itemtype = 'Reaction' if addtype=='R' else 'Transporter'
    
    
    if pnd.isna(row['rstring']): 
        logger.error(f"{itemtype} '{rid}' has no definition (rstring).")
        return 1
    if ' --> ' not in row['rstring'] and ' <=> ' not in row['rstring']:
        logger.error(f"{itemtype} '{rid}' has invalid arrow: '{row['rstring']}'.")
        return 1
        
        
    return 0



def check_curator(logger, mrid, row, db, addtype='R'): 
    
    
    # define itemtype:
    if addtype=='M':
        itemtype = 'Metabolite'
    elif  addtype=='R' :
        itemtype = 'Reaction'
    else: itemtype = 'Transporter'
    
    
    # check if curator was indicated:
    if pnd.isna(row['curator']): 
        logger.error(f"{itemtype} '{mrid}' has no curator.")
        return 1
    
    
    # check if the are valid curators
    curators = set()
    for curator in row['curator'].split(';'):
        curator = curator.strip()
        curators.add(curator)
        if curator not in db['curators']['username'].to_list(): 
            logger.error(f"{itemtype} '{mrid}' has invalid curator: '{curator}'.")
            return 1
    
    
    return list(curators)
    
    
    
def get_curator_notes(logger, row):
    
    
    # notes are separated by ';'
    notes = []
    if pnd.isna(row['notes']) == False:
        for i in row['notes'].strip().split(';'):
            notes.append(i.strip())
        if notes == ['-']:
            notes = []
        
        
    return notes
    
    
    
def add_reaction(logger, model, rid, curators, row, kr_ids, kegg_reaction_to_others, addtype, outdir):
    
    
    # define the itemtype:
    if addtype=='R':
        itemtype = 'Reaction' 
    else: itemtype = 'Transporter'
    
    
    # create a frash reaction
    r = cobra.Reaction(rid)
    model.add_reactions([r])
    r = model.reactions.get_by_id(rid)
    
    
    # copy name and equation:
    r.build_reaction_from_string(row['rstring'])
    r.name = row['name'].strip()
    
    
    # handle bounds:
    if ' --> ' in row['rstring']:
        r.bounds = (0, 1000)
    else:
        r.bounds = (-1000,  1000)
    
    
    # handle GPR
    r.gene_reaction_rule = row['gpr_manual'].strip()
    if r.gene_reaction_rule == 'orphan': 
        # don't want 'orphan' as artificial gene in adition to 'spontaneous'!
        r.gene_reaction_rule = ''    
    r.update_genes_from_gpr()
    
    
    # handle metabolites:
    for m in r.metabolites:
        if m.formula == None or m.charge == None:
            logger.error(f"Metabolite '{m.id}' appears in '{r.id}' but was not previously defined.")
            return 1
        
        
    # write curators as annotations
    r.annotation['curator_codes'] = curators
        
    
    # add annotations to model (same order of Memote)
    ankeys = [
        'rhea', 'kegg.reaction', 'seed.reaction', 'metanetx.reaction', 
        'bigg.reaction', 'reactome', 'ec-code', 'brenda', 'biocyc',         
    ]
    #
    # initialize sets:
    for ankey in ankeys:
        if ankey == 'kegg.reaction': r.annotation[ankey] = set(kr_ids) - set(['RXXXXX'])
        else: r.annotation[ankey] = set()
    #
    # populate sets:
    for kr_id in kr_ids:
        if kr_id != 'RXXXXX':
            if kr_id in kegg_reaction_to_others.keys():
                for ankey in ankeys:
                    r.annotation[ankey].update(kegg_reaction_to_others[kr_id][ankey])
    #
    # save as list: 
    for ankey in ankeys:
        r.annotation[ankey] = list(r.annotation[ankey])
        
        
    # add SBO annotation
    if addtype=='R':
        r.annotation['sbo'] = ['SBO:0000176']  # metabolic reaction
    else:
        r.annotation['sbo'] = ['SBO:0000185']  # transport reaction
        
        
    # add curator notes
    r.annotation['curator_notes'] = get_curator_notes(logger, row)
        
        
    # check if unbalanced
    if r.check_mass_balance() != {}: 
        logger.error(f"{itemtype} '{r.id}' is unbalanced: {r.check_mass_balance()}.")
        return 1    
    
    
    # check if reversible and using ATP
    if r.lower_bound < 0 and r.upper_bound > 0:
        for m in r.metabolites: 
            if m.id.rsplit('_', 1)[0] == 'atp':
                if rid not in get_rids_with_mancheck_balancing():
                    logger.warning(f"Reaction '{rid}' involves ATP and is reversible: are you sure?")
    
    
    return 0
