from mcp.server.fastmcp import FastMCP
import os
from typing import List, Dict, Optional
from typing import List, Dict
from datetime import datetime


# Import tools
from pm_studio_mcp.utils.file_utils import FileUtils
from pm_studio_mcp.utils.search_utils import SearchUtils
from pm_studio_mcp.utils.data_visualization_utils import DataVisualizationUtils
from pm_studio_mcp.skills.feedback_analysis import FeedbackAnalysisUtils
from pm_studio_mcp.utils.auth import AuthUtils
from pm_studio_mcp.utils.chat_utils import ChatUtils
from pm_studio_mcp.utils.calendar_utils import CalendarUtils
from pm_studio_mcp.skills.greeting import GreetingUtils
from pm_studio_mcp.skills.profile_analysis import ProfileAnalyzer
from pm_studio_mcp.utils.titan_metadata_utils import TitanMetadataUtils
from pm_studio_mcp.constant import *
from pm_studio_mcp.utils.crawl_utils import CrawlerUtils
from pm_studio_mcp.utils.titan_query_utils import TitanQuery

# Create MCP server instance
mcp = FastMCP("pm-studio-mcp")  # this is my mcp server name

# Configure WORKING_PATH from environment variables if provided
if 'WORKING_PATH' in os.environ:
    WORKING_PATH = os.environ['WORKING_PATH']
    print(f"Using configured working path: {WORKING_PATH}")
else:
    # Set a default working path for testing
    WORKING_PATH = os.path.join(os.path.dirname(os.path.abspath(__file__)), '../../temp')
    print(f"Using default working path for testing: {WORKING_PATH}")

# Configure Reddit API credentials from environment variables if provided
if 'REDDIT_CLIENT_ID' in os.environ:
    REDDIT_CLIENT_ID = os.environ['REDDIT_CLIENT_ID']
    print("Using configured Reddit client ID from environment")
else:
    # Set a default empty value for testing
    REDDIT_CLIENT_ID = ""
    print("Warning: Using empty Reddit client ID for testing")

if 'REDDIT_CLIENT_SECRET' in os.environ:
    REDDIT_CLIENT_SECRET = os.environ['REDDIT_CLIENT_SECRET']
    print("Using configured Reddit client secret from environment")
else:
    # Set a default empty value for testing
    REDDIT_CLIENT_SECRET = ""
    print("Warning: Using empty Reddit client secret for testing")

# Configure Data.ai API credentials from environment variables if provided
if 'DATA_AI_API_KEY' in os.environ:
    DATA_AI_API_KEY = os.environ['DATA_AI_API_KEY']
    print("Using configured Data.ai API key from environment")
else:
    # Set a default empty value for testing
    DATA_AI_API_KEY = ""
    print("Warning: Using empty Data.ai API key for testing")

if 'DATA_AI_GOOGLE_PLAY_ID' in os.environ:
    DATA_AI_GOOGLE_PLAY_ID = os.environ['DATA_AI_GOOGLE_PLAY_ID']
    print("Using configured Data.ai Google Play ID from environment")
else:
    # Set a default value for testing
    DATA_AI_GOOGLE_PLAY_ID = "com.example.app"
    print(f"Using default Data.ai Google Play ID for testing: {DATA_AI_GOOGLE_PLAY_ID}")

if 'DATA_AI_APP_STORE_ID' in os.environ:
    DATA_AI_APP_STORE_ID = os.environ['DATA_AI_APP_STORE_ID']
    print("Using configured Data.ai App Store ID from environment")
else:
    # Set a default value for testing
    DATA_AI_APP_STORE_ID = "123456789"
    print(f"Using default Data.ai App Store ID for testing: {DATA_AI_APP_STORE_ID}")

# Configure environment variables
if 'USER_ALIAS' in os.environ:
    USER_ALIAS = os.environ['USER_ALIAS']
    print(f"Using configured user alias: {USER_ALIAS}")
else:
    # Set a default user alias for testing
    USER_ALIAS = "default_user"
    print(f"Using default user alias for testing: {USER_ALIAS}")

# Configure Azure AI settings from environment variables
if 'AZURE_INFERENCE_API_KEY' in os.environ:
    AZURE_INFERENCE_API_KEY = os.environ['AZURE_INFERENCE_API_KEY']
    print("Azure API key configured from environment")
else:
    AZURE_INFERENCE_API_KEY = ""
    print("Warning: Azure API key not configured in environment")

if 'AZURE_MODEL_NAME' in os.environ:
    AZURE_MODEL_NAME = os.environ['AZURE_MODEL_NAME']
    print(f"Using Azure model: {AZURE_MODEL_NAME}")
else:
    AZURE_MODEL_NAME = "DeepSeek-R1"
    print(f"Using default Azure model: {AZURE_MODEL_NAME}")

# Create the working directory if it doesn't exist
os.makedirs(WORKING_PATH, exist_ok=True)

@mcp.tool()
async def greeting_with_pm_studio(name: str):  # this is the one of the tool of my MCP server
    """
    Respond to a greeting message with a formatted template.
    """
    return GreetingUtils.greeting_with_pm_studio(name)

# @mcp.tool()
# async def upload_files_to_working_dir_tool(file_paths: List[str]): #handle the file uploaded from the client
#     return upload_files_to_working_dir_tool(file_paths, WORKING_PATH)  # handle the file uploaded from the client


@mcp.tool()
async def ocv_feedback_data_clean_tool(input_files: List[str]):  # Receives file paths generated by the previous tool as parameters
    """
    Clean the OCV user feedback data files provided as input, and output the specified column to new intermediate files.
    Args:
        input_files (List[str]): List of input CSV file paths generated by the previous tool.
    Returns:
        List[str]: List of generated intermediate file paths.
    """
    return FeedbackAnalysisUtils.ocv_feedback_data_clean_tool(input_files, WORKING_PATH)


@mcp.tool()
async def unwrap_feedback_data_clean_tool(input_files: List[str]):  # Receives file paths generated by the previous tool as parameters
    """
    Clean the unwrap user feedback data files provided as input, and output the specified column to new intermediate files.
    Args:
        input_files (List[str]): List of input CSV file paths generated by the previous tool.
    Returns:
        List[str]: List of generated intermediate file paths.
    """
    return FeedbackAnalysisUtils.unwrap_feedback_data_clean_tool(input_files, WORKING_PATH)


@mcp.tool()
async def merge_feedback_data_clean_tool(input_files: List[str]):  # Receives file paths generated by the previous tool as paramters
    """
    Merge the intermediate data files generated by unwrap_feedback_data_clean_tool and ocv_feedback_data_clean_tool.
    Args:
        input_files (List[str]): List of input CSV file paths generated by the previous tools.
    Returns:
        str: Path to the merged output file.
    """
    return FeedbackAnalysisUtils.merge_feedback_data_clean_tool(input_files, WORKING_PATH)


@mcp.tool()
async def write_to_csv_tool(content: str):
    """
    Write the given content to a CSV file.
    Args:
        content (str): The content to write to the CSV file. Content format per line: category,count,original comment 1|original comment 2|...
        output_file (str): The path to the output CSV file.
    """
    output_file = os.path.join(WORKING_PATH, FINAL_RESULT_FILE)
    return FileUtils.write_to_csv_tool(content, output_file)


@mcp.tool()
async def working_dir_cleanup_tool_intermediate():
    """
    Clean up the working directory by deleting all files with _cleaned.csv suffix.
    """
    return FeedbackAnalysisUtils.working_dir_cleanup_tool_intermediate(WORKING_PATH)


@mcp.tool()
async def google_web_tool(keywords: List[str], num_results: int = 10):
    """
    Perform a Google web search with the given query and return top 10 result URLs.

    Args:
        query: Search query
        num_results: Number of search results to return (default: 10)

    Returns:
        List of 10 search result URLs
    """
    return SearchUtils.search_google(keywords, num_results)

@mcp.tool()
async def generate_markdown_tool(content: str, filename: str):
    """
    Write the given content to a Markdown file in the working directory with a customizable filename.

    Args:
        content (str): The content to write to the Markdown file.
        filename (str, optional): The name of the output file.

    Returns:
        str: Path to the saved Markdown file.
    """
    return FileUtils.generate_markdown_tool(content, filename, WORKING_PATH)

@mcp.tool()
async def generate_pie_chart_tool(data: Dict[str, int]):
    """
    Generate a pie chart based on the provided data and save it as 'userfeedback_piechart.jpg'.

    Args:
        data (Dict[str, int]): A dictionary where keys are categories (str) and values are counts (int).

    Returns:
        str: Path to the saved pie chart image.
    """
    return DataVisualizationUtils.generate_pie_chart_tool(data, WORKING_PATH)

@mcp.tool()
async def analyze_user_profiles_tool(file_path: str, max_clusters: int = 10, id_column: str = None):
    """
    Perform cluster analysis on user data to generate user profiles

    Args:
        file_path (str): Path to user data file (Excel or CSV)
        max_clusters (int): Maximum number of clusters, default is 10
        id_column (str, optional): User ID column name, if any

    Returns:
        dict: Dictionary containing paths to all generated files, including cluster data files, cluster summary files, and visualization charts
    """
    result = ProfileAnalyzer.analyze_user_profiles(file_path, WORKING_PATH, max_clusters, id_column)
    if result["status"] == "success":
        # Return all generated file paths
        return {
            "status": "success",
            "message": result["message"],
            "cluster_count": result["cluster_count"],
            "files": result["result_files"]
        }
    else:
        return {
            "status": "error",
            "message": result["message"]
        }

@mcp.tool()
async def analyze_complete_cluster_profiles_tool(input_file: str, cluster_file: str = None, show_all_features: bool = False):
    """
    Analyze complete cluster user profiles, showing feature distribution for each cluster

    Args:
        input_file (str): Path to original data file (with cluster column)
        cluster_file (str, optional): Path to cluster results CSV file
        show_all_features (bool, optional): Whether to display all feature details in terminal

    Returns:
        str: Path to Markdown report file containing complete cluster analysis results
    """
    result = ProfileAnalyzer.analyze_complete_cluster_profiles(input_file, WORKING_PATH, cluster_file, show_all_features)
    if result["status"] == "success":
        return result["result_files"]["markdown_report"]
    else:
        return result["message"]

@mcp.tool()
async def convert_to_markdown_tool(file_path: str):
    """
    Convert a document (doc/excel/ppt/pdf/images/csv/json/xml) to markdown format using MarkItDown.

    Args:
        file_path (str): Path to the input document file

    Returns:
        str: Path to the generated markdown file or error message
    """
    return FileUtils.convert_to_markdown_tool(file_path, WORKING_PATH)

@mcp.tool()
async def scrape_reddit_tool(
    subreddit_name: str,
    keywords: List[str],
    client_id: str = None,
    client_secret: str = None,
    post_limit: int = 100,
    time_filter: str = "month"
):
    """
    Scrape posts from a Reddit subreddit and filter by keywords.

    Args:
        subreddit_name: Name of the subreddit to scrape
        keywords: List of keywords to filter posts by
        client_id: Reddit API client ID (optional, uses REDDIT_CLIENT_ID from environment if not provided)
        client_secret: Reddit API client secret (optional, uses REDDIT_CLIENT_SECRET from environment if not provided)
        post_limit: Maximum number of posts to retrieve (default: 100)
        time_filter: Time filter for posts (choices: "all", "year", "month", "week", "day", "new") (default: "month")

    Returns:
        Dictionary with status and results including path to CSV file with scraped data
    """
    # Use provided credentials or default to environment variables
    used_client_id = client_id if client_id else REDDIT_CLIENT_ID
    used_client_secret = client_secret if client_secret else REDDIT_CLIENT_SECRET

    return SearchUtils.scrape_reddit(
        subreddit_name,
        keywords,
        used_client_id,
        used_client_secret,
        post_limit,
        time_filter,
        WORKING_PATH
    )

@mcp.tool()
async def crawl_website_tool(
    url: str,
    max_pages: int = 5,
    timeout: int = 30,
    selectors: Optional[List[str]] = None,
    deep_crawl: Optional[str] = None,  # Changed from Optional[bool] to Optional[str]
    question: Optional[str] = None
):
    """
    Crawl a website and extract content.

    Args:
        url (str): URL to crawl
        max_pages (int): Maximum number of pages to crawl (default: 5)
        timeout (int): Timeout in seconds for each request (default: 30)
        selectors (List[str], optional): CSS selectors to extract specific content
        deep_crawl (str, optional): Strategy for deep crawling ('bfs' or 'dfs')
        question (str, optional): Specific question for LLM extraction

    Returns:
        dict: Dictionary with crawl results and status including path to output files
    """
    return CrawlerUtils.crawl_website(
        url=url,
        max_pages=max_pages,
        timeout=timeout,
        selectors=selectors,
        working_dir=WORKING_PATH,
        deep_crawl=deep_crawl,
        question=question
    )

@mcp.tool()
async def login():
    """
    start authentication process against MSAL.

    Returns:
        bool: True if authentication is not needed, False otherwise
    """
    return AuthUtils.login()

@mcp.tool()
async def scrape_app_reviews_tool(
    api_key: str = None,
    product_id: str = None,
    market: str = "google-play",
    start_date: str = None,
    end_date: str = None,
    countries: List[str] = None,
    rating: List[int] = None,
    version: str = "all"
):
    """
    Fetch app reviews from data.ai API and save them to a CSV file.

    Args:
        api_key: data.ai API key (optional, uses DATA_AI_API_KEY from environment if not provided)
        product_id: App ID of the product (optional, uses DATA_AI_GOOGLE_PLAY_ID or DATA_AI_APP_STORE_ID if not provided)
        market: Market - one of 'ios', 'mac', or 'google-play' (default: 'google-play')
        start_date: Start date in format YYYY-MM-DD (default: 30 days ago)
        end_date: End date in format YYYY-MM-DD (default: today)
        countries: List of country codes (iOS only)
        rating: List of ratings to filter by (1-5)
        version: App version or 'all' (default: 'all')

    Returns:
        Dictionary with status and results including path to CSV file with scraped app reviews
    """
    # Use provided API key or default to environment variable
    used_api_key = api_key if api_key else DATA_AI_API_KEY

    # Use provided product ID or default based on market
    if not product_id:
        product_id = DATA_AI_GOOGLE_PLAY_ID if market == "google-play" else DATA_AI_APP_STORE_ID

    return SearchUtils.scrape_app_reviews(
        used_api_key,
        product_id,
        market,
        start_date,
        end_date,
        countries,
        rating,
        version,
        WORKING_PATH
    )

@mcp.tool()
async def send_notes_to_myself(message: str):
    """
    Send a note to myself in Microsoft Teams using MS Graph API.
    Args:
        message: Message to send

    Returns:
        dict: Dictionary containing status and response data
    """
    return ChatUtils.send_to_myself(message)

@mcp.tool()
async def send_group_chat_message(topic: str, message: str):
    """
    Send a message to a group chat in Microsoft Teams using MS Graph API.

    Args:
        topic: Topic name of the group chat
        message: Message to send

    Returns:
        dict: Dictionary containing status and response data
    """
    print("topic: ", topic)
    print("message: ", message)
    # Get the chat ID for the group chat
    return  ChatUtils.send_to_group_chat(topic, message)


@mcp.tool()
async def get_calendar_events(start_date: str, end_date: str):
    """
    get the calendar events from Microsoft Graph API.
    Args:
        start_date: Start date in ISO format withh Beijing timezone, e.g. 2023-10-01T00:00:00+08:00
        end_date: End date in ISO format withh Beijing timezone, e.g. 2023-10-31T23:59:59+08:00
    Returns:
        dict: Dictionary containing status and response data
    """
    return CalendarUtils.get_calendar_events(start_date, end_date)

@mcp.tool()
async def query_titan_data_tool(query_str: str, table: str):
    """
    Query data from Titan API and save results to a CSV file.

    Args:
        query_str (str): The query string to execute
        table (str): The table name to query against

    Returns:
        dict: Dictionary containing query results information including:
            - file_path: Path to the saved CSV file
            - row_count: Total number of rows
            - message: Status message
    """
    try:
        titan_query = TitanQuery()
        result = titan_query.query_data_from_titan_tool(
            query_str=query_str,
            table=table,
            output_dir=WORKING_PATH
        )
        return result
    except Exception as e:
        return {
            "error": str(e)
        }

@mcp.tool()
async def get_table_metadata_tool(table_name: str):
    """
    Get metadata information for a specified table and save to JSON file.
    Supports fuzzy matching for table names and SQL template names, making it easy to find tables.
    For tables with enhanced metadata, returns SQL templates and customizable filters.

    Args:
        table_name (str): Table name or keyword (e.g., exact: "EdgeMacECSRetentionV1" or fuzzy: "mac retention")
                          OR SQL template name (e.g., "mac_dau", "retention by browser")

    Returns:
        dict: Dictionary containing query results
            - status: Query status ("success" or "error")
            - message: Status message
            - result_path: Path to the saved JSON file (if successful)
            - table_name: Actual table name found (if using fuzzy matching)
            - sql_templates: Array of SQL templates with placeholders (if available)
            - filter_columns: Dictionary of filter configurations (if available)
            - template_matches: List of templates that match the search (if applicable)
    """
    return TitanMetadataUtils.get_table_metadata_tool(table_name, WORKING_PATH)

@mcp.tool()
async def generate_sql_from_template_tool(table_name: str, template_name: str, filter_values: dict):
    """
    Generate SQL query from a template with provided filter values.

    Args:
        table_name (str): Table name (case-insensitive, supports fuzzy matching)
        template_name (str): Name of the SQL template to use (case-insensitive, partial match supported)
        filter_values (dict): Dictionary of filter values to replace in the template
                           Only filters explicitly provided will be replaced

    Returns:
        dict: Dictionary containing:
            - status: 'success' or 'error'
            - sql: Generated SQL query (if successful)
            - message: Status or error message
            - available_templates: List of available templates (if error due to template not found)
            - matched_table: The actual table name that was matched (if different from input)
    """
    return TitanQuery.generate_sql_from_template(
        table_name=table_name,
        template_name=template_name,
        filter_values=filter_values
    )

def serve():
    mcp.run(transport='stdio')
