from collections import defaultdict
from ilxutils.interlex_sql import IlxSql
import json
import os
import pandas as pd
from pathlib import Path
from rdflib import Graph, RDF, RDFS, OWL, BNode, URIRef, Literal
from sys import exit
from typing import Union, List, Dict, Tuple

from ilxutils.ontopandas import OntoPandas


class InterLexIngestion:

    ''' Goal is to give an additional tool to quick check if ontologies presented already have
        info within InterLex. '''

    def __init__(self, from_backup:bool=False):
        self.sql = IlxSql(db_url=os.environ.get('SCICRUNCH_DB_URL_PRODUCTION'), from_backup=from_backup)
        self.local_degrade = self.sql.local_degrade
        self.terms = self.sql.get_terms()
        self.label2rows = self.sql.get_label2rows()
        self.definition2rows = self.sql.get_definition2rows()
        self.existing_ids = self.sql.get_existing_ids()
        self.ilx2row = self.sql.get_ilx2row()
        self.ilx2existing_ids = self.sql.get_ilx2existing_ids()
        self.iri2row = self.sql.get_iri2row()
        self.curie2row = self.sql.get_curie2row()
        self.fragment2rows = self.sql.get_fragment2rows()
        self.terms_complete = self.sql.get_terms_complete()

    def grab_rdflib_graph_version(g: Graph) -> str:
        ''' Crap-shot for ontology iri if its properly in the header and correctly formated '''
        version = g.subject_objects( predicate = URIRef( OWL.versionIRI ) )
        version = [o for s, o in version]
        if len(version) != 1:
            print('versioning isn\'t correct')
        else:
            version = str(version[0])
            return version

    def fix_ilx(self, ilx_id: str) -> str:
        ''' Database only excepts lower case and underscore version of ID '''
        ilx_id = ilx_id.replace('http://uri.interlex.org/base/', '')
        if ilx_id[:4] not in ['TMP:', 'tmp_', 'ILX:', 'ilx_']:
            raise ValueError(
                'Need to provide ilx ID with format ilx_# or ILX:# for given ID ' + ilx_id)
        return ilx_id.replace('ILX:', 'ilx_').replace('TMP:', 'tmp_')

    def pull_int_tail(self, string: str) -> str:
        ''' Useful for IDs that have giberish in the front of the real ID '''
        int_tail = ''
        for element in string[::-1]:
            try:
                int(element)
                int_tail = element + int_tail
            except:
                pass
        return int_tail

    def extract_fragment(self, iri: str) -> str:
        ''' Pulls only for code/ID from the iri

        I only add the str() conversion for the iri because rdflib objects need to be converted.
        '''
        fragment = str(iri).rsplit('/')[-1].split(':', 1)[-1].split('#', 1)[-1].split('_', 1)[-1]
        return fragment

    def curie_search(self, curie:str) -> dict:
        ''' Returns the row in InterLex associated with the curie

        Note:
            Pressumed to not have duplicate curies in InterLex
        Args:
            curie: The "prefix:fragment_id" of the existing_id pertaining to the ontology
        Returns:
            None or dict
        '''
        ilx_row = self.curie2row.get(curie)
        if not ilx_row:
            return None
        else:
            return ilx_row

    def fragment_search(self, fragement:str) -> List[dict]:
        ''' Returns the rows in InterLex associated with the fragment

        Note:
            Pressumed to have duplicate fragements in InterLex
        Args:
            fragment: The fragment_id of the curie pertaining to the ontology
        Returns:
            None or List[dict]
        '''
        fragement = self.extract_fragment(fragement)
        ilx_rows = self.fragment2rows.get(fragement)
        if not ilx_rows:
            return None
        else:
            return ilx_rows

    def label_search(self, label:str) -> List[dict]:
        ''' Returns the rows in InterLex associated with that label

        Note:
            Pressumed to have duplicated labels in InterLex
        Args:
            label: label of the entity you want to find
        Returns:
            None or List[dict]
        '''
        ilx_rows = self.label2rows(self.local_degrade(label))
        if not ilx_rows:
            return None
        else:
            return ilx_rows

    def readyup_entity(
        self,
        label: str,
        type: str,
        uid: Union[int, str] = None,
        comment: str = None,
        definition: str = None,
        superclass: str = None,
        synonyms: list = None,
        existing_ids: List[dict] = None, ) -> dict:
        ''' Setups the entity to be InterLex ready

        Args:
            label: name of entity
            type: entities type
                Can be any of the following: term, cde, fde, pde, annotation, relationship
            uid: usually fine and auto completes to api user ID, but if you provide one with a
                clearance higher than 0 you can make your own custom. Good for mass imports by one
                person to avoid label collides.
            definition: entities definition
            comment: a foot note regarding either the interpretation of the data or the data itself
            superclass: entity is a sub-part of this entity
                Example: Organ is a superclass to Brain
            synonyms: entity synonyms
            existing_ids: existing curie/iris that link data | couldnt format this easier
        Returns:
            dict
        '''

        entity = dict(
            label = label,
            type = type,
        )

        if uid:
            entity['uid'] = uid

        if definition:
            entity['definition'] = definition

        if comment:
            entity['comment'] = comment

        if superclass:
            entity['superclass'] = {'ilx_id':self.fix_ilx(superclass)}

        if synonyms:
            entity['synonyms'] = [{'literal': syn} for syn in synonyms]

        if existing_ids:
            if existing_ids[0].get('curie') and existing_ids[0].get('iri'):
                pass
            else:
                exit('Need curie and iri for existing_ids in List[dict] form')
            entity['existing_ids'] = existing_ids

        return entity

    def __exhaustive_diff(self, check_list:List[dict]) -> List[List[dict]]:
        ''' Helper for exhaustive checks to see if there any matches at all besides the anchor
            OUTPUT:
                [
                    {
                        'external_ontology_row' : {},
                        'interlex_row' : {},
                        'same': {},
                    },
                    ...
                ],
        '''
        def compare_rows(external_row:dict, ilx_row:dict) -> List[dict]:
            ''' dictionary comparator '''

            def compare_values(string1:Union[str, None], string2:Union[str, None]) -> bool:
                ''' string comparator '''
                if string1 is None or string2 is None:
                    return False
                elif not isinstance(string1, str) or not isinstance(string2, str):
                    return False
                elif string1.lower().strip() != string2.lower().strip():
                    return False
                else: