"""Protein-Tools — Lookup und Sequenzen via UniProt REST API."""

import httpx
from typing import Any


# UniProt REST API Basis-URL
UNIPROT_BASE = "https://rest.uniprot.org/uniprotkb"
# NCBI E-utilities für Sequenzen
NCBI_BASE = "https://eutils.ncbi.nlm.nih.gov/entrez/eutils"
NCBI_PARAMS = {"tool": "bioinformatics-mcp-server", "email": "agent@aiagentkarl.com"}


async def _uniprot_get(endpoint: str, params: dict = None) -> dict[str, Any]:
    """UniProt REST API Anfrage."""
    try:
        async with httpx.AsyncClient(
            timeout=20.0,
            headers={"Accept": "application/json"},
        ) as client:
            response = await client.get(
                f"{UNIPROT_BASE}/{endpoint}",
                params=params or {},
            )
            response.raise_for_status()
            return response.json()
    except Exception as e:
        return {"error": str(e)}


async def _ncbi_fetch_text(endpoint: str, params: dict) -> str:
    """NCBI E-utilities Text-Anfrage für Sequenzen."""
    try:
        merged = {**NCBI_PARAMS, **params}
        async with httpx.AsyncClient(timeout=20.0) as client:
            response = await client.get(f"{NCBI_BASE}/{endpoint}", params=merged)
            response.raise_for_status()
            return response.text
    except Exception as e:
        return f"Fehler: {e}"


def register_protein_tools(mcp) -> None:
    """Registriert alle Protein-Tools."""

    @mcp.tool()
    async def search_proteins(
        query: str,
        organism: str = "human",
        max_results: int = 10,
    ) -> dict:
        """Sucht nach Proteinen in der UniProt-Datenbank.

        Args:
            query: Suchbegriff (z.B. "BRCA1", "insulin receptor", "p53 tumor suppressor")
            organism: Organismus (z.B. "human", "mouse", "E. coli"). Standard: human
            max_results: Maximale Ergebnisanzahl (1-25). Standard: 10
        """
        max_results = min(max(1, max_results), 25)

        # UniProt Suchanfrage
        search_query = f"({query}) AND (organism_name:{organism})"
        data = await _uniprot_get(
            "search",
            {
                "query": search_query,
                "size": max_results,
                "fields": "accession,id,protein_name,gene_names,organism_name,length,reviewed,function",
            },
        )
        if "error" in data:
            return {"fehler": data["error"]}

        results = data.get("results", [])
        if not results:
            return {"fehler": f"Keine Proteine für '{query}' in '{organism}' gefunden"}

        proteine = []
        for p in results:
            # Proteinname extrahieren
            prot_names = p.get("proteinDescription", {})
            rec_name = prot_names.get("recommendedName", {})
            full_name = rec_name.get("fullName", {}).get("value", "")
            if not full_name:
                sub_names = prot_names.get("submittedNames", [])
                if sub_names:
                    full_name = sub_names[0].get("fullName", {}).get("value", "")

            # Gen-Namen
            gene_names = []
            for gn in p.get("genes", []):
                gn_val = gn.get("geneName", {}).get("value", "")
                if gn_val:
                    gene_names.append(gn_val)

            # Funktion (erstes Kommentar)
            funktion = ""
            for kommentar in p.get("comments", []):
                if kommentar.get("commentType") == "FUNCTION":
                    texts = kommentar.get("texts", [])
                    if texts:
                        funktion = texts[0].get("value", "")[:300]
                        break

            proteine.append({
                "accession": p.get("primaryAccession", ""),
                "entry_name": p.get("uniProtkbId", ""),
                "protein_name": full_name,
                "gene_namen": gene_names,
                "organismus": p.get("organism", {}).get("scientificName", ""),
                "laenge": p.get("sequence", {}).get("length", 0),
                "reviewed": p.get("entryType", "") == "UniProtKB reviewed (Swiss-Prot)",
                "funktion": funktion,
                "uniprot_url": f"https://www.uniprot.org/uniprotkb/{p.get('primaryAccession', '')}",
            })

        return {
            "suchbegriff": query,
            "organismus": organism,
            "gefunden": len(proteine),
            "proteine": proteine,
        }

    @mcp.tool()
    async def get_protein_details(accession: str) -> dict:
        """Ruft detaillierte Informationen zu einem Protein via UniProt Accession ab.

        Args:
            accession: UniProt Accession-Nummer (z.B. "P38398" für BRCA1, "P04637" für TP53)
        """
        data = await _uniprot_get(accession)
        if "error" in data:
            return {"fehler": data["error"]}

        # Protein-Name
        prot_names = data.get("proteinDescription", {})
        rec_name = prot_names.get("recommendedName", {})
        full_name = rec_name.get("fullName", {}).get("value", "")
        kurz_name = rec_name.get("shortNames", [{}])[0].get("value", "") if rec_name.get("shortNames") else ""

        # Gen-Namen
        gene_namen = []
        for gn in data.get("genes", []):
            gn_val = gn.get("geneName", {}).get("value", "")
            if gn_val:
                gene_namen.append(gn_val)

        # Funktion
        funktion = ""
        subunit = ""
        for kommentar in data.get("comments", []):
            ct = kommentar.get("commentType", "")
            texts = kommentar.get("texts", [])
            if ct == "FUNCTION" and texts and not funktion:
                funktion = texts[0].get("value", "")[:500]
            elif ct == "SUBUNIT" and texts and not subunit:
                subunit = texts[0].get("value", "")[:300]

        # Keywords
        keywords = [kw.get("name", "") for kw in data.get("keywords", [])[:10]]

        # Sequenz-Info
        seq = data.get("sequence", {})

        return {
            "accession": accession,
            "entry_name": data.get("uniProtkbId", ""),
            "protein_name": full_name,
            "kurzname": kurz_name,
            "gene_namen": gene_namen,
            "organismus": data.get("organism", {}).get("scientificName", ""),
            "taxonomie_id": data.get("organism", {}).get("taxonId", ""),
            "laenge_aminosaeuren": seq.get("length", 0),
            "molekulargewicht": seq.get("molWeight", 0),
            "funktion": funktion,
            "untereinheit": subunit,
            "keywords": keywords,
            "reviewed": data.get("entryType", "") == "UniProtKB reviewed (Swiss-Prot)",
            "sequenz": (seq.get("value", "") or "")[:200] + ("..." if len(seq.get("value", "") or "") > 200 else ""),
            "uniprot_url": f"https://www.uniprot.org/uniprotkb/{accession}",
        }

    @mcp.tool()
    async def get_ncbi_sequence(
        accession: str,
        database: str = "nucleotide",
        sequence_type: str = "fasta",
    ) -> dict:
        """Ruft eine DNA- oder Protein-Sequenz aus NCBI ab.

        Args:
            accession: NCBI Accession-Nummer (z.B. "NM_007294" für BRCA1 mRNA, "NP_009225" für BRCA1 Protein)
            database: Datenbank — "nucleotide" (DNA/RNA) oder "protein". Standard: nucleotide
            sequence_type: Format — "fasta" (Sequenz) oder "gb" (GenBank-Eintrag). Standard: fasta
        """
        valid_dbs = ("nucleotide", "protein")
        if database not in valid_dbs:
            return {"fehler": f"Ungültige Datenbank. Erlaubt: {', '.join(valid_dbs)}"}

        valid_types = ("fasta", "gb")
        if sequence_type not in valid_types:
            return {"fehler": f"Ungültiger Sequenztyp. Erlaubt: {', '.join(valid_types)}"}

        # Zuerst ID suchen
        params = {**NCBI_PARAMS, "retmode": "json"}
        try:
            async with httpx.AsyncClient(timeout=20.0) as client:
                search_resp = await client.get(
                    f"{NCBI_BASE}/esearch.fcgi",
                    params={**params, "db": database, "term": accession},
                )
                search_resp.raise_for_status()
                search_data = search_resp.json()
        except Exception as e:
            return {"fehler": str(e)}

        ids = search_data.get("esearchresult", {}).get("idlist", [])
        if not ids:
            return {"fehler": f"Sequenz '{accession}' in '{database}' nicht gefunden"}

        # Sequenz abrufen
        ret_type = "fasta" if sequence_type == "fasta" else "gb"
        sequenz = await _ncbi_fetch_text(
            "efetch.fcgi",
            {"db": database, "id": ids[0], "rettype": ret_type, "retmode": "text"},
        )

        # FASTA kürzen für Ausgabe
        zeilen = sequenz.strip().split("\n")
        header = zeilen[0] if zeilen else ""
        seq_daten = "".join(zeilen[1:]) if len(zeilen) > 1 else ""

        return {
            "accession": accession,
            "datenbank": database,
            "format": sequence_type,
            "ncbi_id": ids[0],
            "header": header,
            "sequenz_laenge": len(seq_daten),
            "sequenz_vorschau": seq_daten[:300] + ("..." if len(seq_daten) > 300 else ""),
            "vollstaendige_sequenz": seq_daten if len(seq_daten) <= 2000 else seq_daten[:2000] + f"... [{len(seq_daten) - 2000} weitere Zeichen]",
        }
