from supabase import Client, create_client
from openai import AsyncOpenAI
from dotenv import load_dotenv
from datetime import datetime
from functools import wraps
from typing import Optional
import streamlit as st
import webbrowser
import importlib
import inspect
import json
import sys
import os

sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))

# Load environment variables from .env file
load_dotenv()

current_dir = os.path.dirname(os.path.abspath(__file__))
parent_dir = os.path.dirname(current_dir)
workbench_dir = os.path.join(parent_dir, "workbench")

def write_to_log(message: str):
    """Write a message to the logs.txt file in the workbench directory.
    
    Args:
        message: The message to log
    """
    # Get the directory one level up from the current file
    log_path = os.path.join(workbench_dir, "logs.txt")
    os.makedirs(workbench_dir, exist_ok=True)

    timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
    log_entry = f"[{timestamp}] {message}\n"

    with open(log_path, "a", encoding="utf-8") as f:
        f.write(log_entry)

def get_env_var(var_name: str, profile: Optional[str] = None) -> Optional[str]:
    """Get an environment variable from the saved JSON file or from environment variables.
    
    Args:
        var_name: The name of the environment variable to retrieve
        profile: The profile to use (if None, uses the current profile)
        
    Returns:
        The value of the environment variable or None if not found
    """
    # Path to the JSON file storing environment variables
    env_file_path = os.path.join(workbench_dir, "env_vars.json")
    
    # First try to get from JSON file
    if os.path.exists(env_file_path):
        try:
            with open(env_file_path, "r") as f:
                env_vars = json.load(f)
                
                # If profile is specified, use it; otherwise use current profile
                current_profile = profile or env_vars.get("current_profile", "default")
                
                # Get variables for the profile
                if "profiles" in env_vars and current_profile in env_vars["profiles"]:
                    profile_vars = env_vars["profiles"][current_profile]
                    if var_name in profile_vars and profile_vars[var_name]:
                        return profile_vars[var_name]
                
                # For backward compatibility, check the root level
                if var_name in env_vars and env_vars[var_name]:
                    return env_vars[var_name]
        except (json.JSONDecodeError, IOError) as e:
            write_to_log(f"Error reading env_vars.json: {str(e)}")
    
    # If not found in JSON, try to get from environment variables
    return os.environ.get(var_name)

def save_env_var(var_name: str, value: str, profile: Optional[str] = None) -> bool:
    """Save an environment variable to the JSON file.
    
    Args:
        var_name: The name of the environment variable
        value: The value to save
        profile: The profile to save to (if None, uses the current profile)
        
    Returns:
        True if successful, False otherwise
    """
    # Path to the JSON file storing environment variables
    env_file_path = os.path.join(workbench_dir, "env_vars.json")
    os.makedirs(workbench_dir, exist_ok=True)
    
    # Load existing env vars or create empty dict
    env_vars = {}
    if os.path.exists(env_file_path):
        try:
            with open(env_file_path, "r") as f:
                env_vars = json.load(f)
        except (json.JSONDecodeError, IOError) as e:
            write_to_log(f"Error reading env_vars.json: {str(e)}")
            # Continue with empty dict if file is corrupted
    
    # Initialize profiles structure if it doesn't exist
    if "profiles" not in env_vars:
        env_vars["profiles"] = {}
    
    # If no current profile is set, set it to default
    if "current_profile" not in env_vars:
        env_vars["current_profile"] = "default"
    
    # Determine which profile to use
    current_profile = profile or env_vars.get("current_profile", "default")
    
    # Initialize the profile if it doesn't exist
    if current_profile not in env_vars["profiles"]:
        env_vars["profiles"][current_profile] = {}
    
    # Update the variable in the profile
    env_vars["profiles"][current_profile][var_name] = value
    
    # Save back to file
    try:
        with open(env_file_path, "w") as f:
            json.dump(env_vars, f, indent=2)
        return True
    except IOError as e:
        write_to_log(f"Error writing to env_vars.json: {str(e)}")
        return False

def get_current_profile() -> str:
    """Get the current environment profile name.
    
    Returns:
        The name of the current profile, defaults to "default" if not set
    """
    env_file_path = os.path.join(workbench_dir, "env_vars.json")
    
    if os.path.exists(env_file_path):
        try:
            with open(env_file_path, "r") as f:
                env_vars = json.load(f)
                return env_vars.get("current_profile", "default")
        except (json.JSONDecodeError, IOError) as e:
            write_to_log(f"Error reading env_vars.json: {str(e)}")
    
    return "default"

def set_current_profile(profile_name: str) -> bool:
    """Set the current environment profile.
    
    Args:
        profile_name: The name of the profile to set as current
        
    Returns:
        True if successful, False otherwise
    """
    env_file_path = os.path.join(workbench_dir, "env_vars.json")
    os.makedirs(workbench_dir, exist_ok=True)
    
    # Load existing env vars or create empty dict
    env_vars = {}
    if os.path.exists(env_file_path):
        try:
            with open(env_file_path, "r") as f:
                env_vars = json.load(f)
        except (json.JSONDecodeError, IOError) as e:
            write_to_log(f"Error reading env_vars.json: {str(e)}")
            # Continue with empty dict if file is corrupted
    
    # Initialize profiles structure if it doesn't exist
    if "profiles" not in env_vars:
        env_vars["profiles"] = {}
    
    # Initialize the profile if it doesn't exist
    if profile_name not in env_vars["profiles"]:
        env_vars["profiles"][profile_name] = {}
    
    # Set the current profile
    env_vars["current_profile"] = profile_name
    
    # Save back to file
    try:
        with open(env_file_path, "w") as f:
            json.dump(env_vars, f, indent=2)
        return True
    except IOError as e:
        write_to_log(f"Error writing to env_vars.json: {str(e)}")
        return False

def get_all_profiles() -> list:
    """Get a list of all available environment profiles.
    
    Returns:
        List of profile names
    """
    env_file_path = os.path.join(workbench_dir, "env_vars.json")
    
    if os.path.exists(env_file_path):
        try:
            with open(env_file_path, "r") as f:
                env_vars = json.load(f)
                if "profiles" in env_vars:
                    return list(env_vars["profiles"].keys())
        except (json.JSONDecodeError, IOError) as e:
            write_to_log(f"Error reading env_vars.json: {str(e)}")
    
    # Return default if no profiles exist
    return ["default"]

def create_profile(profile_name: str) -> bool:
    """Create a new environment profile.
    
    Args:
        profile_name: The name of the profile to create
        
    Returns:
        True if successful, False otherwise
    """
    env_file_path = os.path.join(workbench_dir, "env_vars.json")
    os.makedirs(workbench_dir, exist_ok=True)
    
    # Load existing env vars or create empty dict
    env_vars = {}
    if os.path.exists(env_file_path):
        try:
            with open(env_file_path, "r") as f:
                env_vars = json.load(f)
        except (json.JSONDecodeError, IOError) as e:
            write_to_log(f"Error reading env_vars.json: {str(e)}")
            # Continue with empty dict if file is corrupted
    
    # Initialize profiles structure if it doesn't exist
    if "profiles" not in env_vars:
        env_vars["profiles"] = {}
    
    # Create the profile if it doesn't exist
    if profile_name not in env_vars["profiles"]:
        env_vars["profiles"][profile_name] = {}
        
        # Save back to file
        try:
            with open(env_file_path, "w") as f:
                json.dump(env_vars, f, indent=2)
            return True
        except IOError as e:
            write_to_log(f"Error writing to env_vars.json: {str(e)}")
            return False
    
    # Profile already exists
    return True

def delete_profile(profile_name: str) -> bool:
    """Delete an environment profile.
    
    Args:
        profile_name: The name of the profile to delete
        
    Returns:
        True if successful, False otherwise
    """
    # Don't allow deleting the default profile
    if profile_name == "default":
        return False
        
    env_file_path = os.path.join(workbench_dir, "env_vars.json")
    
    if os.path.exists(env_file_path):
        try:
            with open(env_file_path, "r") as f:
                env_vars = json.load(f)
                
            if "profiles" in env_vars and profile_name in env_vars["profiles"]:
                # Delete the profile
                del env_vars["profiles"][profile_name]
                
                # If the current profile was deleted, set to default
                if env_vars.get("current_profile") == profile_name:
                    env_vars["current_profile"] = "default"
                
                # Save back to file
                with open(env_file_path, "w") as f:
                    json.dump(env_vars, f, indent=2)
                return True
        except (json.JSONDecodeError, IOError) as e:
            write_to_log(f"Error reading/writing env_vars.json: {str(e)}")
    
    return False

def get_profile_env_vars(profile_name: Optional[str] = None) -> dict:
    """Get all environment variables for a specific profile.
    
    Args:
        profile_name: The name of the profile (if None, uses the current profile)
        
    Returns:
        Dictionary of environment variables for the profile
    """
    env_file_path = os.path.join(workbench_dir, "env_vars.json")
    
    if os.path.exists(env_file_path):
        try:
            with open(env_file_path, "r") as f:
                env_vars = json.load(f)
                
                # If profile is specified, use it; otherwise use current profile
                current_profile = profile_name or env_vars.get("current_profile", "default")
                
                # Get variables for the profile
                if "profiles" in env_vars and current_profile in env_vars["profiles"]:
                    return env_vars["profiles"][current_profile]
                
                # For backward compatibility, if no profiles structure but we're looking for default
                if current_profile == "default" and "profiles" not in env_vars:
                    # Return all variables except profiles and current_profile
                    return {k: v for k, v in env_vars.items() 
                            if k not in ["profiles", "current_profile"]}
        except (json.JSONDecodeError, IOError) as e:
            write_to_log(f"Error reading env_vars.json: {str(e)}")
    
    return {}

def log_node_execution(func):
    """Decorator to log the start and end of graph node execution.
    
    Args:
        func: The async function to wrap
    """
    @wraps(func)
    async def wrapper(*args, **kwargs):
        func_name = func.__name__
        write_to_log(f"Starting node: {func_name}")
        try:
            result = await func(*args, **kwargs)
            write_to_log(f"Completed node: {func_name}")
            return result
        except Exception as e:
            write_to_log(f"Error in node {func_name}: {str(e)}")
            raise
    return wrapper

# Helper function to create a button that opens a tab in a new window
def create_new_tab_button(label, tab_name, key=None, use_container_width=False):
    """Create a button that opens a specified tab in a new browser window"""
    # Create a unique key if none provided
    if key is None:
        key = f"new_tab_{tab_name.lower().replace(' ', '_')}"
    
    # Get the base URL
    base_url = st.query_params.get("base_url", "")
    if not base_url:
        # If base_url is not in query params, use the default localhost URL
        base_url = "http://localhost:8501"
    
    # Create the URL for the new tab
    new_tab_url = f"{base_url}/?tab={tab_name}"
    
    # Create a button that will open the URL in a new tab when clicked
    if st.button(label, key=key, use_container_width=use_container_width):
        webbrowser.open_new_tab(new_tab_url)

# Function to reload the archon_graph module
def reload_archon_graph(show_reload_success=True):
    """Reload the archon_graph module to apply new environment variables"""
    try:
        # First reload pydantic_ai_coder
        import archon.pydantic_ai_coder
        importlib.reload(archon.pydantic_ai_coder)
        
        # Then reload archon_graph which imports pydantic_ai_coder
        import archon.archon_graph
        importlib.reload(archon.archon_graph)

        # Then reload the crawler
        import archon.crawl_pydantic_ai_docs
        importlib.reload(archon.crawl_pydantic_ai_docs)        
        
        if show_reload_success:
            st.success("Successfully reloaded Archon modules with new environment variables!")
        return True
    except Exception as e:
        st.error(f"Error reloading Archon modules: {str(e)}")
        return False        

def get_clients():
    # LLM client setup
    embedding_client = None
    base_url = get_env_var('EMBEDDING_BASE_URL') or 'https://api.openai.com/v1'
    api_key = get_env_var('EMBEDDING_API_KEY') or 'no-api-key-provided'
    provider = get_env_var('EMBEDDING_PROVIDER') or 'OpenAI'
    
    # Setup OpenAI client for LLM
    if provider == "Ollama":
        if api_key == "NOT_REQUIRED":
            api_key = "ollama"  # Use a dummy key for Ollama
        embedding_client = AsyncOpenAI(base_url=base_url, api_key=api_key)
    else:
        embedding_client = AsyncOpenAI(base_url=base_url, api_key=api_key)

    # Supabase client setup
    supabase = None
    supabase_url = get_env_var("SUPABASE_URL")
    supabase_key = get_env_var("SUPABASE_SERVICE_KEY")
    if supabase_url and supabase_key:
        try:
            supabase: Client = Client(supabase_url, supabase_key)
        except Exception as e:
            print(f"Failed to initialize Supabase: {e}")
            write_to_log(f"Failed to initialize Supabase: {e}")

    return embedding_client, supabase      
