#!/usr/bin/env python3

import argparse
import os
import sys
import warnings
from typing import Any, Dict

from dotenv import load_dotenv
from fastmcp import FastMCP

from .cache import BioPortalCache
from .processing import clean_template_response, clean_template_instance_response
from .external_api import (
    async_get_children_from_branch,
    async_get_class_tree,
    async_get_instance,
    async_get_template,
    async_search_instance_ids,
    async_search_terms_from_branch,
    async_search_terms_from_ontology,
)


def main():
    """Entry point for the cedar-mcp CLI."""
    # Load environment variables
    load_dotenv()

    # Create an MCP server
    mcp = FastMCP("cedar-mcp")

    # Parse command-line arguments
    parser = argparse.ArgumentParser(description="CEDAR MCP Python Server")
    parser.add_argument(
        "--cedar-api-key",
        type=str,
        help="(Deprecated) CEDAR API key. Use CEDAR_API_KEY environment variable instead.",
    )
    parser.add_argument(
        "--bioportal-api-key",
        type=str,
        help="(Deprecated) BioPortal API key. Use BIOPORTAL_API_KEY environment variable instead.",
    )
    parser.add_argument(
        "--transport",
        choices=["stdio", "sse", "streamable-http"],
        default="stdio",
        help="Transport protocol (default: stdio)",
    )
    parser.add_argument(
        "--host",
        type=str,
        default="127.0.0.1",
        help="Host to bind to for HTTP transports (default: 127.0.0.1)",
    )
    parser.add_argument(
        "--port",
        type=int,
        default=8000,
        help="Port to bind to for HTTP transports (default: 8000)",
    )
    args = parser.parse_args()

    # Emit deprecation warnings for CLI key flags
    if args.cedar_api_key:
        warnings.warn(
            "--cedar-api-key is deprecated and will be removed in a future release. "
            "Use the CEDAR_API_KEY environment variable instead.",
            DeprecationWarning,
            stacklevel=1,
        )
    if args.bioportal_api_key:
        warnings.warn(
            "--bioportal-api-key is deprecated and will be removed in a future release. "
            "Use the BIOPORTAL_API_KEY environment variable instead.",
            DeprecationWarning,
            stacklevel=1,
        )

    # Use command-line argument if provided, otherwise use environment variable
    CEDAR_API_KEY = args.cedar_api_key or os.getenv("CEDAR_API_KEY")
    if not CEDAR_API_KEY:
        print(
            "Error: CEDAR API key not provided. "
            "Please set the CEDAR_API_KEY environment variable."
        )
        sys.exit(1)

    BIOPORTAL_API_KEY = args.bioportal_api_key or os.getenv("BIOPORTAL_API_KEY")
    if not BIOPORTAL_API_KEY:
        print(
            "Error: BioPortal API key not provided. "
            "Please set the BIOPORTAL_API_KEY environment variable."
        )
        sys.exit(1)

    # Initialize BioPortal cache
    cache = BioPortalCache()

    # Register MCP tools
    @mcp.tool()
    async def get_cedar_template(template_id: str) -> Dict[str, Any]:
        """
        Get a template from the CEDAR repository.

        Args:
            template_id: The template ID or full URL from CEDAR repository
                        (e.g., "https://repo.metadatacenter.org/templates/e019284e-48d1-4494-bc83-ddefd28dfbac")

        Returns:
            Template data from CEDAR, cleaned and transformed
        """
        template_data = await async_get_template(template_id, CEDAR_API_KEY)
        if "error" in template_data:
            return template_data

        # Always clean the response
        template_data = clean_template_response(template_data)

        return template_data

    @mcp.tool()
    async def get_instances_based_on_template(
        template_id: str, limit: int = 10, offset: int = 0
    ) -> Dict[str, Any]:
        """
        Get template instances that belong to the input template ID with pagination support.

        This tool searches for instances of a given template and fetches their complete content
        in paginated chunks to avoid token limit issues.

        Args:
            template_id: The template ID or full URL from CEDAR repository
                        (e.g., "https://repo.metadatacenter.org/templates/e019284e-48d1-4494-bc83-ddefd28dfbac")
            limit: Number of instances to return per page (min: 1, max: 100, default: 10)
            offset: Starting position for pagination (default: 0)

        Returns:
            Dictionary containing:
            - instances: List of template instances for this page
            - pagination: Pagination metadata (total_count, current_page, etc.)
            - errors: List of any errors encountered during fetching
        """
        # Validate pagination parameters
        if limit < 1 or limit > 100:
            return {
                "error": "Invalid limit parameter. Must be between 1 and 100.",
                "instances": [],
                "pagination": None,
            }

        if offset < 0:
            return {
                "error": "Invalid offset parameter. Must be 0 or greater.",
                "instances": [],
                "pagination": None,
            }

        # Step 1: Search for instance IDs with pagination
        search_result = await async_search_instance_ids(
            template_id=template_id,
            cedar_api_key=CEDAR_API_KEY,
            limit=limit,
            offset=offset,
        )

        # Check if search failed
        if "error" in search_result:
            return {
                "error": f"Failed to search for template instances: {search_result['error']}",
                "instances": [],
                "pagination": None,
            }

        instance_ids = search_result.get("instance_ids", [])
        pagination_metadata = search_result.get("pagination", {})

        # If no instances found, return empty result with pagination metadata
        if not instance_ids:
            return {"instances": [], "pagination": pagination_metadata, "errors": None}

        # Step 2: Fetch content for each instance in this page
        instances = []
        failed_instances = []

        for instance_id in instance_ids:
            instance_content = await async_get_instance(instance_id, CEDAR_API_KEY)

            # Check if this instance fetch failed
            if "error" in instance_content:
                failed_instances.append(
                    {"instance_id": instance_id, "error": instance_content["error"]}
                )
            else:
                # Clean the instance content before adding to results
                try:
                    cleaned_instance = clean_template_instance_response(
                        instance_content
                    )
                    instances.append(cleaned_instance)
                except Exception as e:
                    failed_instances.append(
                        {
                            "instance_id": instance_id,
                            "error": f"Failed to clean instance response: {str(e)}",
                        }
                    )

        # Step 3: Prepare response
        response = {"instances": instances, "pagination": pagination_metadata}

        # Include error information if any instances failed to fetch
        if failed_instances:
            response["errors"] = failed_instances

        return response

    @mcp.tool()
    async def term_search_from_branch(
        search_string: str,
        ontology_acronym: str,
        branch_iri: str,
    ) -> Dict[str, Any]:
        """
        Search BioPortal for standardized ontology terms within a specific branch.

        Use this tool to find the correct standardized name and IRI for a given
        term label within a specific ontology branch.

        Args:
            search_string: The term label or keyword to search for
                          (e.g., "aspirin", "glucose")
            ontology_acronym: Ontology acronym to search within
                             (e.g., "CHEBI", "HRAVS")
            branch_iri: IRI of the branch to restrict the search to
                       (e.g., "http://purl.obolibrary.org/obo/CHEBI_23367")

        Returns:
            Search results from BioPortal containing matching terms
        """
        cached = cache.get(
            "search_terms_from_branch",
            search_string=search_string,
            ontology_acronym=ontology_acronym,
            branch_iri=branch_iri,
        )
        if cached is not None:
            return cached

        result = await async_search_terms_from_branch(
            search_string=search_string,
            ontology_acronym=ontology_acronym,
            branch_iri=branch_iri,
            bioportal_api_key=BIOPORTAL_API_KEY,
        )

        if "error" in result:
            return {"error": f"Term search failed: {result['error']}"}

        cache.set(
            "search_terms_from_branch",
            result,
            search_string=search_string,
            ontology_acronym=ontology_acronym,
            branch_iri=branch_iri,
        )

        return result

    @mcp.tool()
    async def term_search_from_ontology(
        search_string: str,
        ontology_acronym: str,
    ) -> Dict[str, Any]:
        """
        Search BioPortal for standardized ontology terms within an entire ontology.

        Use this tool to find the correct standardized name and IRI for a given
        term label across an entire ontology (not restricted to a specific branch).

        Args:
            search_string: The term label or keyword to search for
                          (e.g., "melanoma", "diabetes")
            ontology_acronym: Ontology acronym to search within
                             (e.g., "NCIT", "CHEBI", "DOID")

        Returns:
            Search results from BioPortal containing matching terms
        """
        cached = cache.get(
            "search_terms_from_ontology",
            search_string=search_string,
            ontology_acronym=ontology_acronym,
        )
        if cached is not None:
            return cached

        result = await async_search_terms_from_ontology(
            search_string=search_string,
            ontology_acronym=ontology_acronym,
            bioportal_api_key=BIOPORTAL_API_KEY,
        )

        if "error" in result:
            return {"error": f"Term search failed: {result['error']}"}

        cache.set(
            "search_terms_from_ontology",
            result,
            search_string=search_string,
            ontology_acronym=ontology_acronym,
        )

        return result

    @mcp.tool()
    async def get_branch_children(
        branch_iri: str,
        ontology_acronym: str,
    ) -> Dict[str, Any]:
        """
        Fetch all immediate children terms for a given branch in an ontology.

        Use this tool to retrieve the child terms under a specific branch IRI
        in a BioPortal ontology. This is useful for exploring the hierarchy of
        an ontology or populating dropdown options for a controlled vocabulary.

        Args:
            branch_iri: IRI of the branch to get children for
                       (e.g., "http://purl.obolibrary.org/obo/CHEBI_23367")
            ontology_acronym: Ontology acronym to search within
                             (e.g., "CHEBI", "HRAVS")

        Returns:
            BioPortal response containing child terms with their prefLabels
        """
        result = await async_get_children_from_branch(
            branch_iri=branch_iri,
            ontology_acronym=ontology_acronym,
            bioportal_api_key=BIOPORTAL_API_KEY,
        )

        if "error" in result:
            return {"error": f"Get branch children failed: {result['error']}"}

        return result

    @mcp.tool()
    async def get_ontology_class_tree(
        class_iri: str,
        ontology_acronym: str,
    ) -> Dict[str, Any]:
        """
        Fetch the hierarchical tree structure for a given class in an ontology.

        Use this tool to retrieve the ancestor path and sibling nodes for a
        specific class IRI in a BioPortal ontology. This is useful for
        understanding where a term sits in the ontology hierarchy.

        Args:
            class_iri: IRI of the class to get the tree for
                      (e.g., "http://purl.obolibrary.org/obo/MONDO_0005180")
            ontology_acronym: Ontology acronym to search within
                             (e.g., "MONDO", "CHEBI")

        Returns:
            BioPortal response containing the class tree hierarchy
        """
        cached = cache.get(
            "get_class_tree",
            class_iri=class_iri,
            ontology_acronym=ontology_acronym,
        )
        if cached is not None:
            return cached

        result = await async_get_class_tree(
            class_iri=class_iri,
            ontology_acronym=ontology_acronym,
            bioportal_api_key=BIOPORTAL_API_KEY,
        )

        if "error" in result:
            return {"error": f"Get class tree failed: {result['error']}"}

        cache.set(
            "get_class_tree",
            result,
            class_iri=class_iri,
            ontology_acronym=ontology_acronym,
        )

        return result

    @mcp.tool()
    def remove_stale_cache_entries() -> Dict[str, int]:
        """
        Remove expired entries from the BioPortal search cache.

        This tool cleans up cache entries that have exceeded their TTL
        (time-to-live). Use it to free disk space without losing valid
        cached results.

        Returns:
            Dictionary with removed_count and remaining_count
        """
        return cache.remove_stale()

    @mcp.tool()
    def clear_bioportal_cache() -> Dict[str, int]:
        """
        Clear all entries from the BioPortal search cache.

        Use this tool to force fresh API calls for all subsequent
        BioPortal searches. This is useful when ontology data has been
        updated and you want to ensure the latest results.

        Returns:
            Dictionary with cleared_count
        """
        return cache.clear_all()

    # Start the MCP server
    if args.transport == "stdio":
        print("Starting CEDAR MCP server (stdio)...")
    else:
        print(
            f"Starting CEDAR MCP server ({args.transport}) at http://{args.host}:{args.port} ..."
        )
    mcp.run(transport=args.transport, host=args.host, port=args.port)


if __name__ == "__main__":
    main()
