# pylint:disable=E0402
from datetime import datetime,timedelta
import json
import os
import threading
from typing import Dict, List
import sqlite3

import aiofiles

from ..storage.storage import AIStorage
from ..knowledge.knowledge_base import BaseKnowledgeGraph,ObjFSKnowledgeGrpah
from ..frame.compute_kernel import ComputeKernel
from ..frame.contact_manager import ContactManager
from ..frame.contact import Contact
from ..proto.ai_function import ParameterDefine, SimpleAIFunction
from ..proto.agent_msg import AgentMsg, AgentMsgType
from ..proto.agent_task import AgentWorkLog

from .llm_context import GlobaToolsLibrary
from .chatsession import AIChatSession

import logging

logger = logging.getLogger(__name__)


#class ObjectSummary:
#    def __init__(self) -> None:
#        self.summary : str = None
#        self.object_name : str = None
#        self.priority : int = 5
        # [info_source, info]
#        self.infos : Dict[str,str] = {}



class AgentMemory:
    def __init__(self,agent_id:str,base_dir:str,enable_knowledge_graph = False) -> None:
        self.agent_memory_base_dir = base_dir
        self.agent_id:str= agent_id

        AIStorage.get_instance().ensure_directory_exists(self.agent_memory_base_dir)
        #AIStorage.get_instance().ensure_directory_exists(f"{self.agent_memory_base_dir}/experience")
        #AIStorage.get_instance().ensure_directory_exists(f"{self.agent_memory_base_dir}/contacts")
        #AIStorage.get_instance().ensure_directory_exists(f"{self.agent_memory_base_dir}/relations")
        #AIStorage.get_instance().ensure_directory_exists(f"{self.agent_memory_base_dir}/summary")

        self.memory_db:str = f"{self.agent_memory_base_dir}/memory.db"
        
        self.threshold_hours = 72
        self.last_think_time : float = 0.0
        self.enable_knowledge_graph : bool = enable_knowledge_graph
        if self.enable_knowledge_graph:
            kb_desc = """
The Knowledgegraph is used to store important information obtained by Agent in the conversation.Use the following ways to store information:
- /contacts/$name:Related information of the contact
- /relations/$obj1/$obj2:The relationship between obj2 and obj1
- /summary/$topic:Based on topic summary
            """

            self.knowledge_graph = ObjFSKnowledgeGrpah(f"{self.agent_id}.memory",self.memory_db,kb_desc)
            BaseKnowledgeGraph.add_kb(self.knowledge_graph)
            self.simple_memory_sentences = None
            
        else:
            self.knowledge_graph = None
            self.simple_memory_sentences : List[str] = []

        self.load_memory_meta()


    def _get_conn(self):
        """ get db connection """
        local = threading.local()
        if not hasattr(local, 'conn'):
            local.conn = self._create_connection(self.memory_db)
        return local.conn
    
    def _create_connection(self, db_file):
        """ create a database connection to a SQLite database """
        conn = None
        try:
            conn = sqlite3.connect(db_file)
        except Error as e:
            logging.error("Error occurred while connecting to database: %s", e)
            return None

        if conn:
            self._create_table(conn)

        return conn
    
    def get_session_from_msg(self,msg:AgentMsg) -> AIChatSession:
        if msg.msg_type == AgentMsgType.TYPE_GROUPMSG:
            session_topic = msg.target + "#" + msg.topic
            chatsession = AIChatSession.get_session(self.agent_id,session_topic,self.memory_db)
        else:
            session_topic = msg.get_sender() + "#" + msg.topic
            chatsession = AIChatSession.get_session(self.agent_id,session_topic,self.memory_db)
        return chatsession
    
    # return last record time
    async def load_records(self,starttime,tokenlimit=8000,model_name=None)->float:
        # 专用思路：做聊天记录/工作经验的整理
        # 通用思路：没有具体的目的，让Agent根据提示词自己工作（可能效果很差也可能很好）
        # 先实现通用思路
        msg_records = AIChatSession.load_message_records_by_agentid(self.agent_id,starttime,32,self.memory_db)
        work_records = self.load_worklogs(self.agent_id,token_limit=tokenlimit)
        pass

    async def load_chatlogs(self,msg:AgentMsg,token_limit=800,model_name=""):
        chatsession = self.get_session_from_msg(msg)
        # Must load n (n> = 2), and hope to load the M
        # The information in the # M is gradually added, knowing that it is less than 72 hours from the current time, and consumes enough tokens

        messages_n = chatsession.read_history() # read
        histroy_str = ""
        read_count = 0
        is_all = True
        for msg in messages_n:
            dt = datetime.fromtimestamp(float(msg.create_time))
            formatted_time = dt.strftime('%y-%m-%d %H:%M:%S')
            record_str = f"{msg.sender},[{formatted_time}]\n{msg.body}\n"
            token_limit -= ComputeKernel.llm_num_tokens_from_text(record_str)
            if token_limit <= 32:
                is_all = False
                break
            read_count += 1
            histroy_str = record_str + histroy_str

        return histroy_str,is_all
    
    async def get_chat_summary(self,msg:AgentMsg) -> str:
        chatsession : AIChatSession = self.get_session_from_msg(msg)
        return chatsession.summary
    
    # async def action_chatlog_append(self,params:Dict) -> str:
    #    
    #     input_msg:AgentMsg = params.get("input").get("msg")
    #     llm_result = params.get("llm_result")
    #     chatsession = self.get_session_from_msg(input_msg)
    #     resp_msg = params.get("resp_msg")
    #     if resp_msg:
    #         tags =  llm_result.raw_result.get("tags")
    #         chatsession.append(input_msg,tags)
    #         chatsession.append(resp_msg,tags)
    
    #     return "OK"

    async def load_worklogs(self,operator_id:str,owner_id:str=None, work_types:List[str]=None,token_limit=800):
        conn = self._get_conn()
        c = conn.cursor()
        
        query = 'SELECT * FROM worklog WHERE 1=1'  
        params = []

        if operator_id is not None:
            query += ' AND operator=?'
            params.append(operator_id)

        if owner_id is not None:
            query += ' AND owner_id=?'
            params.append(owner_id)

        if work_types:
            query += ' AND work_type IN ({})'.format(', '.join('?'*len(work_types)))
            params.extend(work_types)
        
        query += ' ORDER BY timestamp DESC LIMIT 8'

        c.execute(query, tuple(params))
        rows = c.fetchall()


        return [self.worklog_from_db_row(row) for row in rows]

    def _create_table(self,conn):
        c = conn.cursor()
        c.execute('''
            CREATE TABLE IF NOT EXISTS worklog (
                logid TEXT PRIMARY KEY,
                owner_id TEXT,
                work_type TEXT,
                timestamp REAL,
                content TEXT,
                result TEXT,
                meta TEXT,  
                operator TEXT
            )
        ''')
        conn.commit()
        #conn.close()

    @classmethod
    def worklog_from_db_row(self,row):
        log = AgentWorkLog()
        # 这里高度依赖表结构的顺序
        log.logid, log.owner_id, log.work_type, log.timestamp, log.content, log.result, meta_str, log.operator = row
        log.meta = json.loads(meta_str) if meta_str else None
        return log
    
    async def append_worklog(self,log:AgentWorkLog)->str:
        conn = self._get_conn()
        c = conn.cursor()
        # 将meta字典转换为JSON字符串
        meta_str = json.dumps(log.meta,ensure_ascii=False) if log.meta else None
        c.execute('''
            INSERT INTO worklog (logid, owner_id, work_type, timestamp, content, result, meta, operator)
            VALUES (?, ?, ?, ?, ?, ?, ?, ?)
        ''', (log.logid, log.owner_id, log.work_type, log.timestamp, log.content, log.result, meta_str, log.operator))
        conn.commit()
        #conn.close()

    def memory_meta_to_dict(self) -> Dict:
        return {
            "last_think_time" : self.last_think_time
        }
    
    def load_meta(self,Dict):
        self.last_think_time = Dict.get("last_think_time",0.0)
        self.simple_memory_sentences = Dict.get("simple_memory_sentences",[])
    
    def load_memory_meta(self):
        meta_file_path = f"{self.agent_memory_base_dir}/meta.json"
        try:
            with open(meta_file_path, mode='r') as file:
                meta = json.load(file)
                self.load_meta(meta)

        except Exception as e:
            logger.error(f"load memory meta failed: {e}")
            self.last_think_time = 0.0

    def save_memory_meta(self):
        meta_file_path = f"{self.agent_memory_base_dir}/meta.json"
        try:
            with open(meta_file_path, mode='w') as file:
                meta = self.memory_meta_to_dict()
                json.dump(meta,file)
        except Exception as e:
            logger.error(f"save memory meta failed: {e}")

    async def get_last_think_time(self)->float:
        return self.last_think_time

    async def set_last_think_time(self,last_time:float):
        self.last_think_time = last_time    
        self.save_memory_meta()

    
    async def get_contact_summary(self,contact_id:str) -> str:
        # There is two part of contact summary
        # Part 1. user defined summary (set by owner or by contac) , global , imutable
        # Part 2. auto generated summary, local in agent memory , mutable

        if contact_id is None:
            return "Contact id is None"
        
        result = {}
        contact_info:Contact = ContactManager.get_instance().find_contact_by_name(contact_id)
        if contact_info:
            result["name"] = contact_info.name
            result["relation"] = contact_info.relationship
            result["notes"]  = contact_info.notes

        # summary_path = f"{self.agent_memory_base_dir}/contacts/{contact_id}.summary"
        # try:
        #     async with aiofiles.open(summary_path, mode='r') as file:
        #         result["summary"] =  await file.read()
                
        # except Exception as e:
        #     logger.error(f"read contact summary failed: {e}")
            
        return json.dumps(result,ensure_ascii=False)
    
    # async def update_contact_summary(self,contact_id:str,summary:str):
    #     summary_path = f"{self.agent_memory_base_dir}/contacts/{contact_id}.summary"
    #     try:
    #         async with aiofiles.open(summary_path, mode='w') as file:
    #             await file.write(summary)
    #             return "OK"
    #     except Exception as e:
    #         logger.error(f"write contact summary failed: {e}")
    #         return "write contact summary failed: {e}"
    
    # async def get_summary(self,object_name:str) -> str:
    #     summary_path = f"{self.agent_memory_base_dir}/{object_name}.summary"
    #     try:
    #         async with aiofiles.open(summary_path, mode='r') as file:
    #             return await file.read()
    #     except Exception as e:
    #         logger.error(f"read summary failed: {e}")
    #         return f"read summary failed: {e}"
        
    # async def update_summary(self,object_name:str,summary:str) -> str:
    #     summary_path = f"{self.agent_memory_base_dir}/{object_name}.summary"
    #     try:
    #         async with aiofiles.open(summary_path, mode='w') as file:
    #             await file.write(summary)
    #             return "OK" 
    #     except Exception as e:
    #         logger.error(f"write summary failed: {e}")
    #         return f"write summary failed: {e}"
    
    # async def list_summary_object_names(self) -> List[str]:
    #     # list dir
    #     try:
    #         contents = os.listdir(self.agent_memory_base_dir)
    #         return [x for x in contents if x.endswith(".summary")]
    #     except Exception as e:
    #         logger.error(f"list summary object names failed: {e}")
    #         return []
        
    # means object1 feel object2 is ... 
    # async def get_relation_summary(self,object_name1:str,object_name2:str) -> str:
    #     summary_path = f"{self.agent_memory_base_dir}/relations/{object_name1}.relation.{object_name2}.summary"
    #     try:
    #         async with aiofiles.open(summary_path, mode='r') as file:
    #             await file.read()
    #     except FileNotFoundError:
    #         return "no summary"
    #     except Exception as e:
    #         logger.error(f"read relation summary failed: {e}")
    #         return f"read relation summary failed: {e}"
        
    
    # async def update_relation_summary(self,object_name1:str,object_name2:str,summary:Dict):
    #     summary_path = f"{self.agent_memory_base_dir}/relations/{object_name1}.relation.{object_name2}.summary"
    #     try:
    #         async with aiofiles.open(summary_path, mode='w') as file:
    #             await file.write(json.dumps(summary))
    #             return "OK"
    #     except Exception as e:
    #         logger.error(f"write relation summary failed: {e}")
    #         return "write relation summary failed: {e}"
    
    # async def get_experience(self,topic_name:str) -> str:
    #     experience_path = f"{self.agent_memory_base_dir}/experience/{topic_name}.experience"
    #     try:
    #         async with aiofiles.open(experience_path, mode='r') as file:
    #             await file.read()
    #     except FileNotFoundError:
    #         return "no experience"
    #     except Exception as e:
    #         logger.error(f"read experience failed: {e}")
    #         return f"read experience failed: {e}"

    
    # async def set_experience(self,topic_name:str,summary:str) -> str:
    #     experience_path = f"{self.agent_memory_base_dir}/experience/{topic_name}.experience"
    #     try:
    #         async with aiofiles.open(experience_path, mode='w') as file:
    #             await file.write(summary)
    #             return "OK"
    #     except Exception as e:
    #         logger.error(f"write experience failed: {e}")
    #         return "write experience failed: {e}"
    
    # async def list_experience(self) -> List[str]:
    #     dir_path = f"{self.agent_memory_base_dir}/experience"
    #     try:
    #         contents = os.listdir(dir_path)
    #         return [x for x in contents if x.endswith(".experience")]
    #     except Exception as e:
    #         logger.error(f"list experience failed: {e}")
    #         return []

    @staticmethod
    def register_ai_functions():
        async def update_chat_summary(parameters):
            agent_memory:AgentMemory = parameters.get("_memory")
            chatsession = AIChatSession.get_session_by_id(parameters.get("session_id"),agent_memory.memory_db)
            summary = parameters.get("summary")
            chatsession.update_summary(summary)
            return "OK"
        parameters = ParameterDefine.create_parameters({
            "session_id": {"type": "string", "description": "session id"},
            "summary": {"type": "string", "description": "new summary"}
        })
        update_chat_summary_func = SimpleAIFunction("agent.memory.update_chat_summary",
                                                    "update chat summary",
                                                    update_chat_summary,
                                                    parameters)
        GlobaToolsLibrary.get_instance().register_tool_function(update_chat_summary_func)

        # async def get_contact_summary(parameters):
        #     agent_memory:AgentMemory = parameters.get("_memory")
        #     contact_name = parameters.get("contact_name")
        #     return await agent_memory.get_contact_summary(contact_name)
        # parameters = ParameterDefine.create_parameters({
        #     "contact_name": {"type": "string", "description": "contact name"}
        # })
        # get_contact_summary_func = SimpleAIFunction("agent.memory.get_contact_summary",
        #                                             "get contact summary",
        #                                             get_contact_summary,
        #                                             parameters)
        # GlobaToolsLibrary.register_tool_function(get_contact_summary_func)

        # async def update_contact_summary(parameters):
        #     agent_memory:AgentMemory = parameters.get("_memory")
        #     contact_name = parameters.get("contact_name")
        #     summary = parameters.get("summary")
        #     return await agent_memory.update_contact_summary(contact_name,summary)
        # parameters = ParameterDefine.create_parameters({
        #     "contact_name": {"type": "string", "description": "contact name"},
        #     "summary": {"type": "string", "description": "new summary"}
        # })
        # update_contact_summary_func = SimpleAIFunction("agent.memory.update_contact_summary",
        #                                             "update contact summary",
        #                                             update_contact_summary,
        #                                             parameters)
        # GlobaToolsLibrary.register_tool_function(update_contact_summary_func)

        # async def get_summary(parameters):
        #     agent_memory:AgentMemory = parameters.get("_memory")
        #     object_name = parameters.get("object_name")
        #     return await agent_memory.get_summary(object_name)
        # parameters = ParameterDefine.create_parameters({
        #     "object_name": {"type": "string", "description": "object name"}
        # })
        # get_summary_func = SimpleAIFunction("agent.memory.get_summary",
        #                                             "get summary of sth",
        #                                             get_summary,
        #                                             parameters)
        # GlobaToolsLibrary.register_tool_function(get_summary_func)

        # async def update_summary(parameters):
        #     agent_memory:AgentMemory = parameters.get("_memory")
        #     object_name = parameters.get("object_name")
        #     summary = parameters.get("summary")
        #     return await agent_memory.update_summary(object_name,summary)
        # parameters = ParameterDefine.create_parameters({
        #     "object_name": {"type": "string", "description": "object name"},
        #     "summary": {"type": "string", "description": "new summary"}
        # })
        # update_summary_func = SimpleAIFunction("agent.memory.update_summary",
        #                                             "update summary of sth",
        #                                             update_summary,
        #                                             parameters)
        # GlobaToolsLibrary.register_tool_function(update_summary_func)

        # async def list_summary_object_names(parameters):
        #     agent_memory:AgentMemory = parameters.get("_memory")
        #     return await agent_memory.list_summary_object_names()
        # parameters = ParameterDefine.create_parameters({})
        # list_summary_object_names_func = SimpleAIFunction("agent.memory.list_summary",
        #                                             "list summary object names",
        #                                             list_summary_object_names,
        #                                             parameters)
        # GlobaToolsLibrary.register_tool_function(list_summary_object_names_func)

        # async def get_relation_summary(parameters):
        #     agent_memory:AgentMemory = parameters.get("_memory")
        #     object_name1 = parameters.get("object1_name")
        #     object_name2 = parameters.get("object2_name")
        #     return await agent_memory.get_relation_summary(object_name1,object_name2)
        # parameters = ParameterDefine.create_parameters({
        #     "object1_name": {"type": "string", "description": "object name1"},
        #     "object2_name": {"type": "string", "description": "object name2"}
        # })
        # get_relation_summary_func = SimpleAIFunction("agent.memory.get_relation_summary",
        #                                             "object1 feel object2 is ...",
        #                                             get_relation_summary,
        #                                             parameters)
        # GlobaToolsLibrary.register_tool_function(get_relation_summary_func)

        # async def update_relation_summary(parameters):
        #     agent_memory:AgentMemory = parameters.get("_memory")
        #     object_name1 = parameters.get("object1_name")
        #     object_name2 = parameters.get("object2_name")
        #     summary = parameters.get("summary")
        #     return await agent_memory.update_relation_summary(object_name1,object_name2,summary)
        # parameters = ParameterDefine.create_parameters({
        #     "object1_name": {"type": "string", "description": "object name1"},
        #     "object2_name": {"type": "string", "description": "object name2"},
        #     "summary": {"type": "string", "description": "new summary"}
        # })
        # update_relation_summary_func = SimpleAIFunction("agent.memory.update_relation_summary",
        #                                             "object1 feel object2 is ...",
        #                                             update_relation_summary,
        #                                             parameters)
        # GlobaToolsLibrary.register_tool_function(update_relation_summary_func)

        # async def get_experience(parameters):
        #     agent_memory:AgentMemory = parameters.get("_memory")
        #     topic_name = parameters.get("topic_name")
        #     return await agent_memory.get_experience(topic_name)
        # parameters = ParameterDefine.create_parameters({
        #     "topic_name": {"type": "string", "description": "topic name"}
        # })
        # get_experience_func = SimpleAIFunction("agent.memory.get_experience",
        #                                             "get experience",
        #                                             get_experience,
        #                                             parameters)
        # GlobaToolsLibrary.register_tool_function(get_experience_func)

        # async def set_experience(parameters):
        #     agent_memory:AgentMemory = parameters.get("_memory")
        #     topic_name = parameters.get("topic_name")
        #     summary = parameters.get("summary")
        #     return await agent_memory.set_experience(topic_name,summary)
        # parameters = ParameterDefine.create_parameters({
        #     "topic_name": {"type": "string", "description": "topic name"},
        #     "summary": {"type": "string", "description": "new summary"}
        # })
        # set_experience_func = SimpleAIFunction("agent.memory.set_experience",
        #                                             "set experience",
        #                                             set_experience,
        #                                             parameters)
        # GlobaToolsLibrary.register_tool_function(set_experience_func)

        # async def list_experience(parameters):
        #     agent_memory:AgentMemory = parameters.get("_memory")
        #     return await agent_memory.list_experience()
        # parameters = ParameterDefine.create_parameters({})
        # list_experience_func = SimpleAIFunction("agent.memory.list_experience",
        #                                             "list exist experience topics",
        #                                             list_experience,
        #                                             parameters)
        # GlobaToolsLibrary.register_tool_function(list_experience_func)

        

    
    
    

    
    
