import os
import numpy as np

from typing import Optional

from starlette.responses import JSONResponse
from starlette.requests import Request
from fastmcp import FastMCP

from abi_core.common.utils import abi_logging
from abi_core.semantic.semantic_access_validator import validate_semantic_access
from layer.embedding_mesh.api import attach_embedding_mesh_routes
from layer.embedding_mesh.embeddings_abi import embed_one, build_agent_card_embeddings
from layer.embedding_mesh.weaviate_store import search_agent_cards

MODEL = os.getenv("MODEL")


def serve(host, port, transport):
    """Start the MCP Agent Card Server
    Args:
        host: Hostname or IP address to bind the Server to.
        port: Port number to bind the server to.
    """
    abi_logging('[🔄] Starting Agents Cards MCP Server')
    mcp = FastMCP('agent-cards', host=host, port=port)

    df = build_agent_card_embeddings()

    @mcp.tool(
        name='find_agent',
        description='Finds the most adecuate agent cards base in natural laguage query string.'
    )
    @validate_semantic_access
    def find_agent(query: str, _request_context: dict = None) -> Optional[dict]:
        """Finds the most relevant Agent Card based on semantic similarity with a natural language query.
        
        Args:
            query (str): Natural language string describing the desired Agent.
        
        Returns:
            dict | None:
                - JSON dictionary of the most relevant Agent Card.
                - None if no matching Agent Card is found.
        
        Search Logic:
            1. Compute embedding for the query using `embed_one()` (local embedding model).
            2. Compute dot product similarity between query embedding and all cached Agent Card embeddings.
            3. Return the Agent Card with the highest similarity score.
        
        Note:
            - Relies on `build_agent_card_embeddings()` having been called at least once.
            - In the robust version, this will query Weaviate instead of in-memory cache.
        """

        if df is None or df.empty:
            abi_logging("[⚠️] No Agent Cards available for search.")
            return None

        query_embedding = embed_one(query)
        resultados_completos = search_agent_cards(query_vector=query_embedding, top_k=1) 

        if resultados_completos:
            best_match = resultados_completos[0]["text"] 
        else:
            best_match = None 

        abi_logging(f"[🎯] Best match index: {best_match}")
        abi_logging(f"✅ Access validated - returning agent: {best_match.get('name', 'Unknown')}")
        return best_match
    
    #@TODO Implement more tools to enhance the system
    
    
    @mcp.resource('resource://agent_cards/list/{agent_id}', mime_type='application/json')
    @validate_semantic_access
    def get_agent_cards(agent_id: str = None,_request_context: dict = None) -> dict:
        """Retrieves all loaded agent cards as a json / dictionary for the MCP resource endpoint.

        This function serves as the handler for the MCP resource identified by
        the URI 'resource://agent_cards/list'.

        Returns:
            A json / dictionary structured as {'agent_cards': [...]}, where the value is a
            list containing all the loaded agent card dictionaries. Returns
            {'agent_cards': []} if the data cannot be retrieved.
        """
        resources = {"agent_cards": []}
        abi_logging(f'[📄] Starting read resources')

        try:
            # df is built once at startup; check availability
            if df is None or getattr(df, "empty", True):
                abi_logging("[⚠️] No Agent Cards available for listing.")
                return resources

            # Extract agent_card column as list
            raw_cards = df.get('agent_card', []).tolist() if 'agent_card' in df.columns else []

            cleaned_cards = []
            for card in raw_cards:
                if card is None:
                    continue
                # If card is a pandas Series or object with to_dict, try to convert to plain dict
                try:
                    # many agent cards will already be dicts
                    if isinstance(card, dict):
                        cleaned_cards.append(card)
                    else:
                        # try to coerce objects (e.g., pandas Series) to dict
                        converted = getattr(card, "to_dict", None)
                        if callable(converted):
                            cleaned_cards.append(converted())
                        else:
                            cleaned_cards.append(card)
                except Exception:
                    # fallback: include raw object (best-effort)
                    cleaned_cards.append(card)

            resources["agent_cards"] = cleaned_cards
            abi_logging(f"[✅] Retrieved {len(cleaned_cards)} agent cards.")
        except Exception as exc:
            abi_logging(f"[❌] Error retrieving agent cards: {exc}")

        return resources
    
    @mcp.resource(
        'resource://agent_cards/{card_name}', mime_type='applicacion/json'
    )
    @validate_semantic_access
    def get_agent_card(card_name: str, _request_context: dict = None) ->dict:
        """Retrieves an specific Agent Card as a JSON dictionary for the MCP resource endpoint

        Returns: JSON Dictionary.
        When resource were found: {agent_card: [.]}
        When data can't be found: {agent_card: []}
        """
        resource = {}
        abi_logging(f'[📄] Starting to read resource {card_name}')
        abi_logging(f'[📋] Available card_uris: {df["card_uri"].tolist()}')
        
        # The card_uri in DataFrame contains file paths, not resource URIs
        # So we need to match by filename instead
        matching_cards = df.loc[
            df['card_uri'].str.contains(f'{card_name}.json', na=False),
            'agent_card'
        ].to_list()
        
        resource['agent_card'] = matching_cards
        abi_logging(f'[✅] Found {len(matching_cards)} matching cards for {card_name}')
        
        if len(matching_cards) == 0:
            abi_logging(f'[⚠️] No cards found for {card_name}. Trying exact filename match...')
            # Try exact filename match
            exact_matches = df.loc[
                df['card_uri'].str.endswith(f'{card_name}.json'),
                'agent_card'
            ].to_list()
            resource['agent_card'] = exact_matches
            abi_logging(f'[🎯] Exact match found {len(exact_matches)} cards for {card_name}')
        
        return resource
    
    @mcp.custom_route("/health", methods=["GET"])
    async def health(request: Request):
        return JSONResponse({"status": "ok"})
    
    mcp.run(transport=transport)