from __future__ import unicode_literals, division, absolute_import, print_function

__license__   = 'GPL v3'
__copyright__ = '2025, Digital Assassins'
import sqlite3
import time, re, random, os
from six import text_type as unicode
from six.moves.urllib.parse import quote
try:
    from queue import Empty, Queue
except ImportError:
    from Queue import Empty, Queue
from collections import OrderedDict

from lxml.html import fromstring
from datetime import datetime

from calibre import as_unicode
from calibre.constants import numeric_version as calibre_version
from calibre.ebooks.metadata import check_isbn
from calibre.ebooks.metadata.sources.base import Source
from calibre.utils.icu import lower
from calibre.utils.cleantext import clean_ascii_chars
from calibre.utils.localization import get_udc
from calibre.utils.config_base import prefs
from calibre.library import db
from calibre.utils.localization import canonicalize_lang

from calibre.ebooks.metadata.book.base import Metadata

class AIMetadata(Source):

    name = 'AI Metadata'
    description = 'Sends each ebook to an A.I. LLM Chat Bot and asks it to read the book, retrieve the metadata from the book itself, then summarise the book in its own words.'
    author = 'Digital Assassins'
    version = (0, 1, 1)
    minimum_calibre_version = (8, 4, 0)

    IDENT_NAME = 'aimodel'
    capabilities = frozenset(['identify'])
    touched_fields = frozenset(['title', 'authors', 'identifier:' + IDENT_NAME,
        'identifier:isbn', 'comments', 'publisher', 'pubdate'
        ])
    has_html_comments = False
    supports_gzip_transfer_encoding = True
    
    # DB variables
    DB = None
    CURSOR = None
    CURRENT_BOOK_ID = None
    calibre_library = prefs['library_path'] + "/"
    calibre_library = calibre_library.replace('/', os.sep)
    
    
    def config_widget(self):
        '''
        Overriding the default configuration screen for our own custom configuration
        '''
        from calibre_plugins.ai_metadata.config import ConfigWidget
        return ConfigWidget(self)
    
    def is_configured(self):
        import calibre_plugins.ai_metadata.config as cfg
        if cfg.plugin_prefs[cfg.STORE_NAME][cfg.KEY_PLATFORM_API_KEY] != "" and cfg.plugin_prefs[cfg.STORE_NAME][cfg.KEY_PLATFORM_URL] != "" and cfg.plugin_prefs[cfg.STORE_NAME][cfg.KEY_PLATFORM_WORKSPACE] != "":
            return True
        else:
            return False
    
    def instanciateDatabase(self, log):
        global DB
        global CURSOR
        
        #log.info(self.calibre_library)
        
        if self.DB == None:
            try:
                self.DB = sqlite3.connect(self.calibre_library + "metadata.db")
                self.CURSOR = self.DB.cursor()
                log.info("Database connection successful!")
                return True
            except Exception as e:
                log.info(f"Error connecting to the database: {e}")
                return False
        else:
            return True
    
    #def get_ebook_file_by_ISBN(self, ISBN, log):
        
    
    def get_ebook_file_by_ID(self, title, authors, identifiers, log):
        
        cdb = db(self.calibre_library).new_api
        
        omi = Metadata(title, authors)
        omi.identifiers = identifiers
        book_ids = cdb.find_identical_books(omi)
        log.info("Book IDs:")
        log.info(book_ids)
        if book_ids != None:
            # first instanciate the database
            if self.instanciateDatabase(log):
                
                paths = {}
                ## now do a search on the title in the database
                sql_query = 'SELECT books.id, path, format, name FROM `books` LEFT JOIN `data` ON books.id = data.book WHERE books.id = "'+ str( next(iter(book_ids) )) +'" AND (data.format = "PDF" OR data.format = "EPUB" OR data.format = "TXT")'
                response = self.CURSOR.execute(sql_query)
                results = response.fetchall()
                
                for result in results:
                    #log.info("Search Result:")
                    #log.info(result)
                    
                    self.CURRENT_BOOK_ID = result[0]
                    path_part = result[1]                
                    file_format = result[2]                
                    file_name = result[3]
                    
                    book_path = self.calibre_library + path_part + "/" + file_name
                    book_path = book_path.replace('/', os.sep)
                    format_lower = file_format.lower()
                    book_path = book_path + "." + format_lower
                    paths[file_format] = book_path
                
                self.closeDB()            
                return paths
            else:
                # couldnt connect to database
                return False
        else:
            return False
        
    def get_ebook_file_by_title(self, title, log):
        
        # first instanciate the database
        if self.instanciateDatabase(log):
            paths = {}
            ## now do a search on the title in the database
            sql_query = 'SELECT books.id, path, format, name FROM `books` LEFT JOIN `data` ON books.id = data.book WHERE books.title = "'+ title +'" AND (data.format = "PDF" OR data.format = "EPUB" OR data.format = "TXT")'
            response = self.CURSOR.execute(sql_query)
            results = response.fetchall()
            
            for result in results:
                #log.info("Search Result:")
                #log.info(result)
                
                self.CURRENT_BOOK_ID = result[0]
                path_part = result[1]                
                file_format = result[2]                
                file_name = result[3]
                
                book_path = self.calibre_library + path_part + "/" + file_name
                book_path = book_path.replace('/', os.sep)
                format_lower = file_format.lower()
                book_path = book_path + "." + format_lower
                paths[file_format] = book_path
            
            self.closeDB()            
            return paths
        else:
            # couldnt connect to database
            return False
    
    def get_book_file_by_type(self, paths):
        if "EPUB" in paths:
            return paths["EPUB"]
        elif "PDF" in paths:
            return paths["PDF"]
        else:
            return False
    
    def closeDB(self):
        if self.DB != None:
            self.DB.close()
            self.DB = None
            self.CURSOR = None
            
    def instanciate_api_adapter(self):
        try: self.api_adapter
        except AttributeError: self.api_adapter = None
        if self.api_adapter == None:
            import calibre_plugins.ai_metadata.anything_llm as anythingllm
            import calibre_plugins.ai_metadata.config as cfg
            self.AI_PLATFORM = cfg.plugin_prefs[cfg.STORE_NAME][cfg.KEY_AI_PLATFORM]
            if self.AI_PLATFORM == "AnythingLLM":
                self.api_adapter = anythingllm.AnythingLLM()
    
    def check_llm_platform_running(platform):
            return self.api_adapter.authorize()
    
    def response_check(self, value):
        if value == None or value == False:
            return False
        if isinstance(value, dict) or isinstance(value, list):
            value = ",".join(value)
        if isinstance(value, str):
            value = value.lower()
            bad_responses = ["none", "n/a", "false", "not available", "does not feature", "doesn't feature", "does not include", "doesn't include", "undefined", "un-defined"]
            if any(elem in value for elem in bad_responses):
                return False
        return True
    
    def tag_sanitise(self, value):
        if value == None:
            return None
        if isinstance(value, dict) or isinstance(value, list):
            value = "/".join(value)
        return value.split("/")

    def author_sanitise(self, value):
        if value == None:
            return None
        if isinstance(value, dict) or isinstance(value, list):
            value = "/".join(value)
        value = value.split("/")
        if isinstance(value, list):
            return value
        else:
            return [value]
    
    def summary_sanitise(self, value):
        if value == None:
            return None
        if isinstance(value, dict) or isinstance(value, list):
            value = " ".join(value)
            return value
        if isinstance(value, str):
            return value
    def language_sanitise(self,value):
        if value == None or value == "Not Available":
            return None
        if isinstance(value, dict):
            return None
        if isinstance(value, list):
            if len(value) > 0:
                if value[0] != "Not Available":
                    value = ",".join(value)
                    return value
                else:
                    return None
            else:
                return None
        if isinstance(value, str):
            #return canonicalize_lang(value)
            return value
            
    def identify(self, log, result_queue, abort, title=None, authors=None,
            identifiers={}, timeout=300):
        
        matches = []
        
        self.instanciate_api_adapter()
        if title != None:
            paths = self.get_ebook_file_by_title(title, log)
        else:
            paths = self.get_ebook_file_by_ID(title, authors, identifiers, log)
            
        '''elif identifiers.get('isbn', None) != None:
            print("Parent:")
            #print(parent.book_id)
            log.info("Queue:")
            log.info(result_queue)
            book_id = 0
            isbn = check_isbn(identifiers.get('isbn', None))
            paths = self.get_ebook_file_by_ISBN(book_id, log)
        '''
            
        BOOK_FILE = self.get_book_file_by_type(paths)
        log.info("Book File:" + str(BOOK_FILE) )
        if BOOK_FILE != False:
            
            response = self.api_adapter.send_chat_message_to_ai(BOOK_FILE)
            if response == False:
                raise Exception("Couldn't get a response from LLM, are your settings correct ?")                    
            else:
                log.info(response)
                print(response)
                
                # instanciate the metadata class
                mi = Metadata(title, authors)
                
                if response.get('title', None) != None:
                    mi.title = response.get('title', None)
                if response.get('author', None) != None:
                    if self.response_check( response.get('author', None) ):
                        mi.authors = self.author_sanitise( response.get('author', None) )
                if response.get('isbn', None) != None:
                    if self.response_check( response.get('isbn', None) ):
                        mi.isbn = response.get('isbn', None)
                if response.get('publisher', None) != None:
                    if self.response_check( response.get('publisher', None) ):
                        mi.publisher = response.get('publisher', None)
                if response.get('summary', None) != None:
                    mi.comments = self.summary_sanitise( response.get('summary', None) )
                if response.get('genre', None) != None:
                    if self.response_check( response.get('genre', None) ):
                        mi.tags = self.tag_sanitise( response.get('genre', None) )
                if response.get('language', None) != None:
                    mi.language = self.language_sanitise( response.get('language', None) )               
                pub_date = response.get('publicationdate', None)
                if pub_date != None and pub_date != "Not Available":
                    from calibre.utils.date import parse_date, utcnow
                    try:
                        default = utcnow().replace(day=15)
                        mi.pubdate = parse_date(pub_date, assume_utc=True, default=default)
                    except:
                        log.error('Failed to parse pubdate %r' % pub_date)
                
                # add the returned data to the result queue
                result_queue.put(mi)
            try: 1==1        
            except Exception as e:
                log.info(f"Couldn't get response from Platform: {e}")
                self.closeDB()
                return
                
            self.closeDB()
        else:
            self.closeDB()
            return
        
        if abort.is_set():
            self.closeDB()
            return

        return None