"""Agentic research engine - coordinates agent skills and Ollama LLM interactions."""
from __future__ import annotations

import json
import os
import re
from typing import Any
import httpx

from app.agents.skills import SKILLS, Skill
from app.agents.tools import TOOLS
from app.config import get_settings


class Researcher:
    """Main research orchestrator that coordinates agents and tools."""

    def __init__(self):
        self.settings = get_settings()
        self.active_skills: list[Skill] = []
        self.findings: list[dict] = []
        self.context: dict = {}

    def select_skills(self, query: str, skill_names: list[str] | None = None) -> list[str]:
        """Select relevant agent skills for the query. Defaults to all if none specified."""
        if skill_names:
            selected = []
            for name in skill_names:
                s = SKILLS.get(name)
                if s:
                    selected.append(name)
            return selected

        query_lower = query.lower()
        selected = []
        for name, skill in SKILLS.items():
            for token in skill.verb_tokens:
                if token in query_lower:
                    selected.append(name)
                    break
        # If no token matched, default to researcher + qa
        if not selected:
            selected = ["researcher", "qa_agent"]
        return selected

    async def call_ollama(self, prompt: str, model: str | None = None, system: str | None = None) -> str:
        """Call Ollama LLM with streaming support."""
        model = model or self.settings.gpt_oss_model
        messages = []
        if system:
            messages.append({"role": "system", "content": system})
        messages.append({"role": "user", "content": prompt})

        async with httpx.AsyncClient(timeout=300) as client:
            resp = await client.post(
                f"{self.settings.ollama_url}/api/chat",
                json={"model": model, "messages": messages, "stream": False},
            )
            resp.raise_for_status()
            return resp.json().get("message", {}).get("content", "")

    async def run_skill(
        self, skill_name: str, query: str, doc_id: str | None = None
    ) -> str:
        """Execute a single agent skill."""
        skill = SKILLS.get(skill_name)
        if not skill:
            return f"[Unknown skill: {skill_name}]"

        system_prompt = f"""You are the {skill_name.replace('_', ' ').title()} agent.
{skill.instructions}"""

        tool_prompts = []
        for tool_name in skill.tools:
            tool_fn = TOOLS.get(tool_name)
            if not tool_fn:
                continue
            import inspect
            sig = inspect.signature(tool_fn)
            try:
                if tool_name == "read_document" and doc_id:
                    result = await tool_fn(doc_id)
                elif tool_name == "read_chunks" and doc_id:
                    result = await tool_fn(doc_id, limit=20)
                elif tool_name == "get_page_text" and doc_id:
                    result = await tool_fn(doc_id, page_num=0)
                elif tool_name == "extract_facts" and doc_id:
                    result = await tool_fn(doc_id, question=query)
                elif tool_name == "list_documents":
                    result = await tool_fn()
                elif tool_name in ("vector_search", "text_search", "memory_similarity_search"):
                    kwargs = {"query": query}
                    if doc_id and "doc_id" in sig.parameters:
                        kwargs["doc_id"] = doc_id
                    result = await tool_fn(**kwargs)
                elif tool_name in ("get_findings", "get_memories", "get_pipeline_state"):
                    result = await tool_fn(session_id=doc_id or "")
                elif tool_name == "save_finding":
                    result = await tool_fn(session_id=doc_id or "", question=query, answer="", finding_type="research", confidence=0.5)
                else:
                    kwargs = {}
                    if "doc_id" in sig.parameters and doc_id:
                        kwargs["doc_id"] = doc_id
                    if "query" in sig.parameters:
                        kwargs["query"] = query
                    result = await tool_fn(**kwargs)
                tool_prompts.append(f"\n--- {tool_name} output ---\n{result}")
            except Exception as e:
                tool_prompts.append(f"\n--- {tool_name} error ---\n{str(e)}")

        context = "".join(tool_prompts)
        
        prompt = f"""Research query: {query}
Document ID: {doc_id}
Context from tools:
{context}

Provide your analysis following the {skill_name} protocol."""

        response = await self.call_ollama(prompt, system=system_prompt)
        
        self.findings.append({
            "skill": skill_name,
            "query": query,
            "response": response,
            "doc_id": doc_id,
            "timestamp": str(os.popen('date +%Y-%m-%dT%H:%M:%S').read()).strip(),
        })
        
        return response

    async def run_research_session(
        self, query: str, session_id: str,
        doc_id: str | None = None,
        skill_names: list[str] | None = None
    ) -> dict[str, str]:
        """Run a full research session with selected agents."""
        selected_skills = self.select_skills(query, skill_names)
        results = {}
        
        for skill_name in selected_skills:
            results[skill_name] = await self.run_skill(skill_name, query, doc_id)
        
        return results

    async def cross_reference(self, query: str, doc_ids: list[str]) -> str:
        """Cross-reference content across multiple documents."""
        contexts = []
        for doc_id in doc_ids:
            chunks = await TOOLS["read_chunks"](doc_id)
            contexts.append(f"--- {doc_id} ---\n{chunks[:2000]}")
        
        combined = "\n\n".join(contexts)
        prompt = f"""Cross-reference these documents for the query: {query}

{combined}

Direct comparison. No preamble."""
        
        return await self.call_ollama(prompt, system="You are a cross-reference analyst. Output concise, comparative findings.")


# ── Pipeline skills ────────────────────────────────

PIPELINE_STAGES = ("document_triage", "evidence_extraction", "research_synthesis")


async def _parse_triage_output(response: str) -> dict:
    """Parse document-triage output into structured dict."""
    obj = {}
    for section in ["OBJECTIVE", "SUB-QUESTIONS", "CLASSIFICATION", "READING_ORDER", "EXTRACTION_CRITERIA", "RISKS_AND_GAPS"]:
        marker = f"[{section}]"
        # Find start of this section
        start = response.find(marker)
        end = response.find("[", start + len(marker)) if start != -1 else -1
        if end != -1:
            chunk = response[start + len(marker):end].strip()
        elif start != -1:
            chunk = response[start + len(marker):].strip()
        else:
            chunk = ""
        obj[section] = chunk
    return obj


async def _parse_evidence_output(response: str) -> list[dict]:
    """Parse evidence-extraction output into structured list."""
    rows = []
    # Find [EVIDENCE_ROWS] section
    start_marker = "[EVIDENCE_ROWS]"
    start = response.find(start_marker)
    # Check if there's a cross-comparison section
    cross_start = response.find("[CROSS_COMPARISON]")
    end = cross_start if cross_start != -1 else len(response)
    if start == -1:
        start = 0
    section = response[start:end].strip()
    
    lines = section.split("\n")
    for line in lines:
        line = line.strip()
        if not line or line.startswith("["):
            continue
        parts = [p.strip() for p in line.split("|")]
        if len(parts) >= 7:
            rows.append({
                "topic": parts[1],
                "evidence_type": parts[2],
                "description": parts[3],
                "trace_ref": parts[4],
                "evidence": parts[5],
                "analyst_note": parts[6],
                "confidence": parts[7] if len(parts) > 7 else "Medium",
                "review_needed": parts[8] if len(parts) > 8 else "No",
            })
    return rows


async def _parse_synthesis_mode(response: str) -> str:
    """Determine output mode from synthesis response content."""
    for mode in ["brief", "report", "gap analysis", "matrix"]:
        if mode in response.lower():
            return mode
    return "brief"


class ResearchPipeline:
    """Pipeline orchestrator: document_triage → evidence_extraction → research_synthesis."""

    def __init__(self):
        self.settings = get_settings()
        self.plan: dict = {}
        self.evidence: list[dict] = []
        self.synthesis: str = ""
        self.findings: list[dict] = []

    async def run(
        self,
        query: str,
        session_id: str,
        db,
        doc_ids: list[str] | None = None,
        output_mode: str | None = None,
    ) -> dict:
        """Run all three pipeline stages sequentially, persisting after each."""
        results = {}

        # ── Stage 1: document_triage ─────────────────────
        triage_skill = SKILLS["document_triage"]
        system = f"You are document_triage. {triage_skill.instructions}"
        
        # Gather context from available documents
        if doc_ids:
            doc_context = []
            for did in doc_ids:
                doc_info = await TOOLS["read_document"](did)
                doc_context.append(str(doc_info))
            context_input = "\n".join(doc_context)
        else:
            doc_list = await TOOLS["list_documents"]()
            context_input = doc_list
        
        triage_prompt = f"""Research query: {query}
Context from tools:
{context_input}

Apply the document-tireage protocol."""

        triage_output = await self._call_ollama(triage_prompt, system=system)
        self.plan = await _parse_triage_output(triage_output)
        self.findings.append({
            "stage": "triage",
            "output": triage_output,
            "state": self.plan,
        })
        await db.save_pipeline_stage(session_id, "triage", triage_output, self.plan)
        results["triage"] = triage_output
        results["triage_state"] = self.plan

        # ── Stage 2: evidence_extraction ────────────────
        evidence_skill = SKILLS["evidence_extraction"]
        system = f"""You are evidence_extraction. {evidence_skill.instructions}

Triage plan (from previous stage):
{json.dumps(self.plan, indent=2, default=str)}

Focus on extracting evidence for the sub-questions and extraction criteria defined above."""

        # Read chunks from all relevant documents
        all_chunks = []
        for did in (doc_ids or []):
            chunks = await TOOLS["read_chunks"](did)
            all_chunks.append(f"--- Document {did} ---\n{chunks}")
        
        context_input = "\n\n".join(all_chunks) if all_chunks else "No documents loaded yet."
        
        evidence_prompt = f"""Research query: {query}
Sub-questions to address:
{self.plan.get('SUB-QUESTIONS', 'N/A')}

Context from tools:
{context_input}

Apply the evidence_extraction protocol. Return structured evidence rows."""

        evidence_output = await self._call_ollama(evidence_prompt, system=system)
        self.evidence = await _parse_evidence_output(evidence_output)
        
        # Persist to DB
        if self.evidence:
            await db.save_structured_evidence(session_id, self.evidence)
        await db.save_pipeline_stage(session_id, "evidence", evidence_output, {"row_count": len(self.evidence)})
        
        results["evidence"] = evidence_output
        results["evidence_rows"] = self.evidence

        # ── Stage 3: research_synthesis ─────────────────
        synthesis_skill = SKILLS["research_synthesis"]
        
        mode = output_mode or await _parse_synthesis_mode(evidence_output)
        mode_prompts = {
            "brief": "Use Research Brief output mode.",
            "report": "Use Research Report output mode.",
            "gap": "Use Gap Analysis output mode.",
            "matrix": "Use Comparison Matrix output mode.",
        }
        mode_instruct = mode_prompts.get(mode, mode_prompts["brief"])
        
        system = f"""You are research_synthesis. {synthesis_skill.instructions}

{mode_instruct}

Extracted evidence (from previous stage):
{json.dumps(self.evidence, indent=2, default=str)[:15000]}

Apply the research_synthesis protocol."""

        synthesis_prompt = f"""Research query: {query}

Sub-questions:
{self.plan.get('SUB-QUESTIONS', 'N/A')}

Apply the research_synthesis protocol."""

        synthesis_output = await self._call_ollama(synthesis_prompt, system=system)
        
        self.synthesis = synthesis_output
        synthesis_state = {
            "mode": mode,
            "sub_questions": self.plan.get("SUB-QUESTIONS", ""),
            "evidence_count": len(self.evidence),
        }
        await db.save_pipeline_stage(session_id, "synthesis", synthesis_output, synthesis_state)
        
        results["synthesis"] = synthesis_output
        results["output_mode"] = mode
        results["pipeline_complete"] = True

        self.findings.append({
            "stage": "synthesis",
            "output": synthesis_output,
            "state": synthesis_state,
        })
        
        return results

    async def _call_ollama(self, prompt: str, system: str) -> str:
        """Call Ollama LLM."""
        messages = [
            {"role": "system", "content": system},
            {"role": "user", "content": prompt},
        ]
        async with httpx.AsyncClient(timeout=600) as client:
            resp = await client.post(
                f"{self.settings.ollama_url}/api/chat",
                json={"model": self.settings.gpt_oss_model, "messages": messages, "stream": False},
            )
            resp.raise_for_status()
            return resp.json().get("message", {}).get("content", "")


    async def render_pipeline_results(self, results: dict) -> list[dict]:
        """Render pipeline results for frontend display."""
        sections = []
        
        # Triage stage
        if "triage_state" in results:
            plan = results["triage_state"]
            sections.append({
                "stage": "triage",
                "title": "Stage 1: Source Triage",
                "objective": plan.get("OBJECTIVE", ""),
                "sub_questions": plan.get("SUB-QUESTIONS", ""),
                "classification": plan.get("CLASSIFICATION", ""),
                "reading_order": plan.get("READING_ORDER", ""),
                "extraction_criteria": plan.get("EXTRACTION_CRITERIA", ""),
                "risks_gaps": plan.get("RISKS_AND_GAPS", ""),
            })
        
        # Evidence stage
        if "evidence_rows" in results:
            rows = results["evidence_rows"]
            sections.append({
                "stage": "evidence",
                "title": f"Stage 2: Extracted Evidence ({len(rows)} rows)",
                "rows": rows,
            })
        
        # Synthesis stage
        if "synthesis" in results:
            sections.append({
                "stage": "synthesis",
                "title": f"Stage 3: Research Synthesis (mode: {results.get('output_mode', 'auto')})",
                "synthesis": results["synthesis"],
            })
        
        return sections
