import json
import time
import os
import threading
from openai import OpenAI
from flask import Flask, jsonify, request, render_template, send_from_directory

import emails
import manychat
import airtable
import functions
import assistants

#@app.route('/manychat', methods=['POST']) #CHAT WEBHOOK ASYNC
#@app.route('/voiceflow', methods=['POST']) #CHAT WEBHOOK SYNC
#@app.route('/memory', methods=['POST']) #CHAT WEBHOOK NO SAVE
#@app.route('/management', methods=['POST']) #MANAGEMENT PAGE
#@app.route('/demo', methods=['POST']) #DEMO PAGE
#@app.route('/delay', methods=['POST']) #MANYCHAT DELAY
#@app.route('/processreport', methods=['POST']) #MANYCHAT DELAY

# Initialize Flask app, so the endpoint routes can be called by ManyChat and Voiceflow
app = Flask(__name__, template_folder='web_templates', static_folder='web_templates', static_url_path='')
app.debug = True

# Configure Flask logging to flask.log (even in debug mode)
import logging
from logging.handlers import RotatingFileHandler

# Setup Flask access logging to flask.log
file_handler = RotatingFileHandler('flask.log', maxBytes=10240, backupCount=10)
file_handler.setFormatter(logging.Formatter(
    '%(asctime)s - %(message)s'
))
file_handler.setLevel(logging.INFO)

# Add handler to werkzeug logger (handles HTTP requests)
werkzeug_logger = logging.getLogger('werkzeug')
werkzeug_logger.addHandler(file_handler)
werkzeug_logger.setLevel(logging.INFO)

@app.route('/')
def home():
    return '<h1>autocolant.serban.eu.com API</h1>'


#ENDPOINT: Async chat function for Manychat, it sends the user input, this responds with OK, then goes to the background process to send the request to OpenAI. When done it returns the response to Manychat through their API.
# Requires a valid bob_id ini file in the bobs folder.
@app.route('/manychatasync', methods=['POST'])
def manychatasync():
  # Start timer for logging the OpenAI response time
  request_start_time = time.time()

  data = request.json
  thread_id = data.get('thread_id')
  user_input = data.get('user_message')
  bob_id = data.get('bob_id')
  platform = data.get('platform')
  contact_id = data.get('contact_id')

  if not bob_id:
    print("ERROR: Bob ID not found in request data.")
    return jsonify({"error": "Missing assistant_id"}), 400

  bob = functions.load_bob_config(bob_id)

  if not bob:
    return jsonify({"response": "error"}), 400

  OPENAI_API_KEY = bob['OpenAI']['API_KEY']
  BOB_ASS_ID = bob['OpenAI']['ass_id']

  client = OpenAI(api_key=OPENAI_API_KEY)

  if functions.empty_thread_id(thread_id):
    print("INFO:  Creating new thread_id in /manychat.")
    thread = client.beta.threads.create()
    thread_id = thread.id

  if not thread_id:
    print("ERROR: Failed to create new thread.")
    return jsonify({"error": "Failed to create new thread."}), 400
  
  #Continue the rest in the background, which will call back the ManChat API with the response when ready
  thread = threading.Thread(target=manychat.background_process,
                            args=(bob, client, thread_id, BOB_ASS_ID,
                                  user_input, platform, contact_id,
                                  request_start_time))
  thread.start()

  return jsonify({"thread_id": thread_id, "status": "completed"}), 200


#ENDPOINT: Synchronous chat function for Voiceflow, it sends the user input, this responds with the response when it gets it back from OpenAI.
# Requires a valid bob_id ini file in the bobs folder.
@app.route('/voiceflow', methods=['POST'])
def voiceflow():
  data = request.json
  thread_id = data.get('thread_id')
  user_input = data.get('user_message')
  assistant_id = data.get('assistant_id')
  platform = data.get('platform')

  if not assistant_id:
    print("ERROR: Assistant ID not found in request data.")
    return jsonify({"error": "Missing assistant_id"}), 400

  bob = functions.load_bob_config(assistant_id)

  if not bob:
    return jsonify({"response": "error"}), 400

  OPENAI_API_KEY = bob['OpenAI']['API_KEY']
  BOB_ASS_ID = bob['OpenAI']['ass_id']

  client = OpenAI(api_key=OPENAI_API_KEY)

  if functions.empty_thread_id(thread_id):
    thread = client.beta.threads.create()
    thread_id = thread.id

  # Start timer for OpenAI request
  request_start_time = time.time()

  # Start run and send run ID back to Voiceflow
  message = client.beta.threads.messages.create(thread_id=thread_id,
                                                role="user",
                                                content=user_input)

  run = client.beta.threads.runs.create_and_poll(thread_id=thread_id,
                                                 assistant_id=BOB_ASS_ID)
  run_id = run.id
  agent_id = BOB_ASS_ID

  if not thread_id or not run_id:
    print("ERROR: Missing thread_id or run_id")
    return jsonify({"response": "error"})

  start_time = time.time()
  while time.time() - start_time < 60:
    run_status = client.beta.threads.runs.retrieve(thread_id=thread_id,
                                                   run_id=run_id)

    if run_status.status == 'completed':
      messages = client.beta.threads.messages.list(thread_id=thread_id)
      message_content = messages.data[0].content[0].text
      # Remove annotations
      annotations = message_content.annotations
      for annotation in annotations:
        message_content.value = message_content.value.replace(
            annotation.text, '')

      agent_reply = message_content.value
      request_end_time = time.time()
      request_duration = request_end_time - request_start_time

      tokens = run_status.usage.total_tokens

      thread = threading.Thread(target=airtable.save_reply,
                                args=(bob, thread_id, agent_id, user_input,
                                      agent_reply, request_duration, platform,
                                      tokens))
      thread.start()

      return jsonify({
          "thread_id": thread_id,
          "response": agent_reply,
          "status": "completed"
      })

    if run_status.status == 'requires_action':
      # Handle the function call
      for tool_call in run_status.required_action.submit_tool_outputs.tool_calls:
        if tool_call.function.name == "create_lead":
          # Process lead creation
          arguments = json.loads(tool_call.function.arguments)

          thread = threading.Thread(
              target=airtable.create_lead,
              args=(bob, thread_id, arguments["nume"],
                    arguments["telefon"], arguments["adresa"], platform))
          thread.start()

          output = 'LEAD CONTACT INFO SAVED AND EMAILED TO THE BUSINESS'
          client.beta.threads.runs.submit_tool_outputs(thread_id=thread_id,
                                                       run_id=run_id,
                                                       tool_outputs=[{
                                                           "tool_call_id":
                                                           tool_call.id,
                                                           "output":
                                                           json.dumps(output)
                                                       }])
          
        if tool_call.function.name == "search_products":
            # Process the 'search_products' function call by the agent
            arguments = json.loads(tool_call.function.arguments)
            output = functions.search_products(arguments["query"])
            client.beta.threads.runs.submit_tool_outputs(
                thread_id=thread_id,
                run_id=run_id,
                tool_outputs=[{
                     "tool_call_id":
                     tool_call.id,
                     "output":
                     json.dumps(output)
                 }])



    
    time.sleep(1)

  print("ERROR: /chat run timed out")
  airtable.save_orphan_reply(bob, thread_id, agent_id, user_input,
                             request_duration, platform)
  return jsonify({"response": "timeout"}), 400


#ENDPOINT: For chatboat owners to chat with their history agent from the client page, and without saving the chat history
# Requires a valid bob_id ini file in the bobs folder.
@app.route('/memory', methods=['POST'])
def memory():
  data = request.json
  thread_id = data.get('thread_id')
  user_input = data.get('user_message')
  assistant_id = data.get('assistant_id')
  platform = data.get('platform')

  bob = functions.load_bob_config(assistant_id)

  if not bob:
    return jsonify({"response": "error"}), 400

  OPENAI_API_KEY = bob['OpenAI']['API_KEY']
  MEMORY_ASS_ID = bob['OpenAI']['memory_id']

  client = OpenAI(api_key=OPENAI_API_KEY)

  if functions.empty_thread_id(thread_id):
    print("INFO: Creating new thread_id in /memory")
    thread = client.beta.threads.create()
    thread_id = thread.id

  # Start timer for OpenAI request
  request_start_time = time.time()

  # Start run and send run ID back to ManyChat/Voiceflow
  message = client.beta.threads.messages.create(thread_id=thread_id,
                                                role="user",
                                                content=user_input)

  run = client.beta.threads.runs.create_and_poll(thread_id=thread_id,
                                                 assistant_id=MEMORY_ASS_ID)
  run_id = run.id
  print("Run started with ID:", run.id)

  if not thread_id or not run_id:
    print("ERROR: Missing thread_id or run_id")
    return jsonify({"response": "error"}), 400

  # Start timer ensuring no more than 9 seconds, ManyChat timeout is 10s
  start_time = time.time()
  while time.time() - start_time < 60:
    run_status = client.beta.threads.runs.retrieve(thread_id=thread_id,
                                                   run_id=run_id)
    print("Checking run status:", run_status.status)

    if run_status.status == 'completed':
      messages = client.beta.threads.messages.list(thread_id=thread_id)
      message_content = messages.data[0].content[0].text
      # Remove annotations
      annotations = message_content.annotations
      for annotation in annotations:
        message_content.value = message_content.value.replace(
            annotation.text, '')

      #save the thread_id, user_input, agent_reply
      agent_reply = message_content.value

      return jsonify({
          "thread_id": thread_id,
          "response": agent_reply,
          "status": "completed"
      })

    time.sleep(1)

  print("ERROR: Run timed out")
  return jsonify({"response": "timeout"})


#ENDPOINT: Private page for chatboat owners to see their bob's CRM, history and stats.
@app.route('/management', methods=['POST'])
def management():
  data = request.json
  bob_id = data.get('bob')

  #call airtable with the client id and return the client page data if valid
  # Requires a valid bob_id ini file in the bobs folder.
  bob = functions.load_bob_config(bob_id)

  if not bob:
    return jsonify({"response": "error"}), 400

  BOB_NAME = bob['ID']['name']
  BOB_ASS_ID = bob['OpenAI']['ass_id']
  VF_MEMORY_PID = bob['Voiceflow']['memory_PID']
  AIRTABLE_APP_ID = bob['Airtable']['app_ID']
  M_LEADS_URL = bob['Airtable']['monthly_leads_URL']
  M_CONV_URL = bob['Airtable']['monthly_conv_URL']
  LEADS_C_URL = bob['Airtable']['leads_chart_URL']
  CONV_MEMORY_URL = bob['Airtable']['conv_memory_URL']
  LEADS_CRM_URL = bob['Airtable']['leads_crm_URL']

  return jsonify({
      "BOB_NAME":
      BOB_NAME,
      "BOB_ASS_ID":
      BOB_ASS_ID,
      "VF_MEMORY_PID":
      VF_MEMORY_PID,
      "M_LEADS_URL":
      'https://airtable.com/embed/' + AIRTABLE_APP_ID + "/" + M_LEADS_URL,
      "M_CONV_URL":
      'https://airtable.com/embed/' + AIRTABLE_APP_ID + "/" + M_CONV_URL,
      "LEADS_C_URL":
      'https://airtable.com/embed/' + AIRTABLE_APP_ID + "/" + LEADS_C_URL,
      "CONV_MEMORY_URL":
      'https://airtable.com/embed/' + AIRTABLE_APP_ID + "/" + CONV_MEMORY_URL,
      "LEADS_CRM_URL":
      'https://airtable.com/embed/' + AIRTABLE_APP_ID + "/" + LEADS_CRM_URL,
      "status":
      "completed"
  })


#ENDPOINT: Private page for chatboat owners to see their bob's CRM, history and stats.
@app.route('/login', methods=['POST'])
def login():
  data = request.json
  mode = data.get('mode')
  bob_id = data.get('value')

  

  #call airtable with the client id and return the client page data if valid
  # Requires a valid bob_id ini file in the bobs folder.
  bob_id = functions.check_bob_login(login, password)

  if not bob:
    return jsonify({"response": "error"}), 400

  BOB_NAME = bob['ID']['name']
  BOB_ASS_ID = bob['OpenAI']['ass_id']
 
  return jsonify({
      "BOB_NAME":
      BOB_NAME,
      "BOB_ASS_ID":
      BOB_ASS_ID,
      "status":
      "completed"
  })


#ENDPOINT: Public page for chatboat testers to talk to their bob on the web.
# Requires a valid bob_id ini file in the bobs folder.
@app.route('/demo', methods=['POST'])
def demo():
  data = request.json
  bob_id = data.get('bob')

  #call airtable with the client id and return the client page data if valid
  bob = functions.load_bob_config(bob_id)

  if not bob:
    return jsonify({"response": "error"}), 400

  BOB_NAME = bob['ID']['name']
  BOB_ASS_ID = bob['OpenAI']['ass_id']
  VF_BOB_PID = bob['Voiceflow']['bob_PID']

  return jsonify({
      "BOB_NAME": BOB_NAME,
      "BOB_ASS_ID": BOB_ASS_ID,
      "VF_BOB_PID": VF_BOB_PID,
      "status": "completed"
  })


@app.route('/processreport', methods=['POST'])
def processreport():

  data = request.json
  bob_id = data.get('bob')

  #call airtable with the client id and return the client page data if valid
  bob = functions.load_bob_config(bob_id)

  if not bob:
    return jsonify({"response": "error"}), 400

  print(f"INFO: Processing threads for bob: " + bob_id)
  BOB_NAME = bob['ID']['name']
  BOB_ASS_ID = bob['OpenAI']['ass_id']
  VF_BOB_PID = bob['Voiceflow']['bob_PID']

  threads = airtable.get_unprocessed_threads(bob)

  conversations_summary = ''
  leads_summary = ''

  #make summaries for each for /threads
  for thread in threads:

    thread_id = thread.get("fields", {}).get("thread_id")
    conv_time = thread.get("fields", {}).get("Created")
    
    print(f"INFO: Starting to process thread: " + thread_id)

    name, platform = airtable.get_user_and_platform(bob, thread_id)
    conv_summary, conversation, conv_tokens = airtable.summarize_thread(
        bob, thread_id, platform, name)

    conversations_summary += conv_time + "<br> Platform: " + platform + "<br> Tokens: " + str(conv_tokens) + '<br>'
    conversations_summary += conv_summary + '<br><br>'

    lead_summary = airtable.get_lead_summary(bob, thread_id)

    if lead_summary is None:
            lead_summary = ''
    else:
      leads_summary += lead_summary + '<br>'


  
  no_leads = len(leads_summary.splitlines()) - 1
  no_convs = len(threads)
  print(f"INFO: Finished processing this many threads: " + str(no_convs))

  if no_convs == 0:
    return jsonify({
        "BOB_NAME": BOB_NAME,
        "status": "none"
    })

  functions.append_memory(bob, leads_summary + conversations_summary)
  assistants.upload_knowledge_base(bob, leads_summary + conversations_summary)
  emails.send_report(bob, no_leads, no_convs, leads_summary,
                     conversations_summary)

  for thread in threads:
    thread_id = thread.get("fields", {}).get("thread_id")
    if thread_id:
      airtable.mark_processed(bob, thread_id)
      print(f"INFO: Marking processed thread: " + thread_id)

  return jsonify({
      "BOB_NAME": BOB_NAME,
      "status": "completed"
  })


#ENDPOINT: delay function because fuck you ManyChat
@app.route('/delay', methods=['POST'])
def delay():
  time.sleep(4)  # Enjoy a precious custom 4 second delay
  return jsonify({"message": "Fuck you ManyChat"})


if __name__ == '__main__':
  port = int(os.environ.get('PORT', 5001))
  app.run(host='0.0.0.0', port=port, debug=False)
