import logging
import json
import time
from typing import Dict, Any, List, Optional
import os

from app.db.models.agent import AgentInstance, AgentTemplate
from app.db import models
from app.core.orchestration import Architect

logger = logging.getLogger(__name__)

class HarnessEvaluator:
    def __init__(self, db, agent_id, mesh_node_id, sync_workspace_id, llm_provider, services):
        self.db = db
        self.agent_id = agent_id
        self.mesh_node_id = mesh_node_id
        self.sync_workspace_id = sync_workspace_id
        self.llm_provider = llm_provider
        self.services = services
        
        # Resolve orchestrator assistant from services container
        self.orchestrator = getattr(services, "orchestrator", None)
        self.assistant = None
        if self.orchestrator:
            self.assistant = self.orchestrator.assistant

    async def initialize_cortex(self):
        """Creates .cortex/ directory and initializes history.log if missing."""
        if not self.assistant or not self.mesh_node_id:
            logger.warning(f"[HarnessEvaluator] Assistant or mesh_node_id missing; skipping .cortex init.")
            return

        # Ensure directory exists
        self.assistant.dispatch_single(
            self.mesh_node_id, 
            "mkdir -p .cortex", 
            session_id=self.sync_workspace_id
        )
        
        # Reset history for a fresh evaluation session
        self.assistant.write(
            self.mesh_node_id, 
            ".cortex/history.log", 
            "[]", 
            session_id=self.sync_workspace_id
        )
        
        # Reset feedback for the new run
        self.assistant.write(
            self.mesh_node_id, 
            ".cortex/feedback.md", 
            "# Session Started\n", 
            session_id=self.sync_workspace_id
        )

    async def log_event(self, name: str, details: str = "", duration: float = 0, event_type: str = "event", metadata: Dict = None):
        """Records a generic event to the history log."""
        if not self.assistant: return
        try:
            cmd_res = self.assistant.dispatch_single(
                self.mesh_node_id, 
                "cat .cortex/history.log", 
                session_id=self.sync_workspace_id,
                timeout=5
            )
            
            history = []
            if cmd_res.get("status") == "SUCCESS":
                try:
                    history = json.loads(cmd_res.get("stdout", "[]"))
                except:
                    history = []
            
            history.append({
                "type": event_type,
                "name": name,
                "details": details,
                "duration": round(duration, 2),
                "timestamp": time.time(),
                "metadata": metadata or {}
            })
            
            self.assistant.write(
                self.mesh_node_id, 
                ".cortex/history.log", 
                json.dumps(history, indent=2), 
                session_id=self.sync_workspace_id
            )
        except Exception as e:
            logger.error(f"[HarnessEvaluator] Event logging failed: {e}")

    async def generate_rubric(self, initial_prompt: str):
        """Stage 1: Pre-Execution. Generate a task-specific rubric.md."""
        if not self.assistant: return None
        start = time.time()
        
        # --- File-Based Knowledge Discovery (Aligned with ClaudeCode) ---
        coworker_context = ""
        try:
            cmd_res = self.assistant.dispatch_single(
                self.mesh_node_id, 
                "cat .coworker.md", 
                session_id=self.sync_workspace_id,
                timeout=5
            )
            if cmd_res.get("status") == "SUCCESS":
                coworker_context = f"\n\nPROJECT-SPECIFIC CONTEXT (from .coworker.md):\n{cmd_res.get('stdout', '')}"
        except:
            logger.debug("[HarnessEvaluator] No .coworker.md found; proceeding with generic rubric.")

        system_prompt = f"""You are a Quality Control Architect for a live infrastructure swarm. 
Your task is to analyze a user request and generate a specific Evaluation Rubric in Markdown.

## Context Discovery (Architectural Constraints):
{coworker_context or "No specific .coworker.md found. Use general best practices for modern infrastructure and swarm orchestration."}

The Rubric MUST include:
1. **Expectations**: A checklist of specific results the agent should satisfy for this specific task.
2. **Core Rubric**: A quantitative scoring guide (0-100) across these dimensions:
   - **Quality**: Tone, structure, and readability.
   - **Accuracy**: Completeness and technical correctness.
   - **Efficiency (Non-AI Alike)**: Adherence to edicts.

Format as a clean Markdown file with exactly one '# Evaluation Rubric' title."""

        user_prompt = f"Target Prompt: \"{initial_prompt}\"\n\nConstruct the request-specific rubric.md now."
        
        try:
            messages = [
                {"role": "system", "content": system_prompt},
                {"role": "user", "content": user_prompt}
            ]
            prediction = await self.llm_provider.acompletion(messages=messages, stream=False)
            rubric_content = prediction.choices[0].message.content
            
            # Save to node
            self.assistant.write(
                self.mesh_node_id, 
                ".cortex/rubric.md", 
                rubric_content, 
                session_id=self.sync_workspace_id
            )
            
            await self.log_event("Rubric Generation", "Task-specific evaluation criteria established.", duration=time.time() - start)
            return rubric_content
        except Exception as e:
            logger.error(f"[HarnessEvaluator] Rubric generation failed: {e}")
            return None

    async def evaluate_blind(self, initial_prompt: str, rubric_content: str, result_content: str) -> Dict[str, Any]:
        """Stage 2A: The Blind Rating (Absolute Objectivity). Uses tools to inspect results."""
        start = time.time()
        
        system_prompt = f"""You are the Co-Worker Evaluator (Blind Auditor). 
Your goal is to perform a BLIND evaluation of the Main Agent's work.
You have NO knowledge of previous rounds or internal reasoning. You only see the goal and the result.

Original Request: {initial_prompt}

Current Result:
---
{result_content}
---

Rubric:
{rubric_content}

EDICTS:
- Don't add features/refactors beyond what was asked.
- Don't add docstrings/comments not explicitly requested.
- Don't create helpers/utilities for one-time operations.

MISSION:
1. Review the Current Result above.
2. If the task involves code or files, explore the workspace using your tools (ls, cat, etc.) to verify truth.
3. Assign a numerical score (0-100) and a brief justification.
Your final response MUST end with exactly: FINAL_SCORE: [number]"""

        res = await self._run_evaluator_agent(system_prompt, "Perform Blind Evaluation of the result state.")
        res["duration"] = time.time() - start
        return res

    async def evaluate_delta(self, initial_prompt: str, rubric_content: str, blind_justification: str, history_log: List[Dict[str, Any]], transcript: str) -> str:
        """Stage 2B: The Delta Analysis. Identifies gaps by comparing result to reasoning transcript."""
        start = time.time()
        
        historical_context = "Historical Rework Instructions (Gap Context):\n"
        for entry in history_log:
            if entry.get("type") == "attempt":
                historical_context += f"- Attempt {entry['round']}: {entry.get('reason', 'N/A')}\n"

        system_prompt = f"""You are the Co-Worker Quality Architect (Delta Analyst).
The Blind Evaluator assigned a score based solely on the file result, but now we must bridge the gap.
You see the FULL mental transcript of how the Main Agent reached this state.

Original Request: {initial_prompt}

Rubric:
{rubric_content}

Blind Evaluation Justification:
{blind_justification}

{historical_context}

Main Agent Execution Transcript:
---
{transcript}
---

MISSION:
1. Identify the 'Delta' (what was intended vs. what was actually committed).
2. Spot 'Gold-Plating' (did they do extra work not asked for?).
3. Format feedback as ACTIONABLE COMMANDS (Directives).
Example: "Directive: Refactor auth.py:L10 to use settings.API_KEY instead of hardcoded string."

Format as Markdown. Start with '# Rework Instructions'."""

        try:
            messages = [
                {"role": "system", "content": system_prompt},
                {"role": "user", "content": "Analyze the delta and generate the Directive-based rework feedback."}
            ]
            prediction = await self.llm_provider.acompletion(messages=messages, stream=False)
            feedback = prediction.choices[0].message.content
            
            # Content as return-only to allow orchestrator to aggregate reports
            return feedback
        except Exception as e:
            logger.error(f"[HarnessEvaluator] Delta analysis failed: {e}")
            return f"Error during Delta Analysis: {str(e)}"

    async def _run_evaluator_agent(self, system_prompt: str, user_request: str) -> Dict[str, Any]:
        """Utility to run a context-stripped Architect loop for verification."""
        architect = Architect()
        
        # Resolve tools for the evaluator (same as parent session)
        tool_service = getattr(self.services.rag_service, "tool_service", None)
        tools = []
        user_id = "agent-system"
        if tool_service:
            instance = self.db.query(AgentInstance).filter(AgentInstance.id == self.agent_id).first()
            if instance and instance.session:
                user_id = instance.session.user_id
                tools = tool_service.get_available_tools(self.db, user_id, feature="agent_harness", session_id=instance.session_id)

        # --- Global Blueprint Discovery (Hub Manifesto) ---
        global_manifesto = ""
        import os
        manifesto_path = "/app/blueprints/manifesto.md"
        if os.path.exists(manifesto_path):
            try:
                with open(manifesto_path, "r") as f:
                    global_manifesto = f"\n\nGLOBAL HUB MANIFESTO (Project Vision):\n{f.read()}"
            except:
                pass

        # --- Dynamic Knowledge Snapshot (Discovery Step) ---
        dynamic_snapshot = ""
        try:
            # We take a 'Live Snapshot' of the node's status (similar to ClaudeCode's gitStatus)
            snap_res = self.assistant.dispatch_single(
                self.mesh_node_id, 
                "uname -a && uptime && df -h /", 
                session_id=self.sync_workspace_id,
                timeout=5
            )
            if snap_res.get("status") == "SUCCESS":
                dynamic_snapshot = f"\n\nLIVE SYSTEM SNAPSHOT (Discovery Step):\n{snap_res.get('stdout', '')}"
        except:
            pass

        # --- File-Based Knowledge Discovery ---
        context_chunks = []
        # Inject Global Manifesto if found
        if global_manifesto:
            context_chunks.append({
                "id": "hub_manifesto",
                "content": global_manifesto,
                "metadata": {"source": "hub_blueprints", "priority": "critical"}
            })

        # Inject the live snapshot as a transient knowledge chunk
        if dynamic_snapshot:
            context_chunks.append({
                "id": "runtime_telemetry",
                "content": dynamic_snapshot,
                "metadata": {"source": "runtime_discovery", "priority": "high"}
            })

        try:
            cmd_res = self.assistant.dispatch_single(
                self.mesh_node_id, 
                "cat .coworker.md", 
                session_id=self.sync_workspace_id,
                timeout=5
            )
            if cmd_res.get("status") == "SUCCESS":
                context_chunks.append({
                    "id": ".coworker.md",
                    "content": cmd_res.get("stdout", ""),
                    "metadata": {"source": "local_filesystem", "priority": "high"}
                })
        except:
            pass

        final_answer = ""
        score = 0
        
        final_answer = ""
        # Run Architect with a strictly limited profile to ensure snappy evaluation
        # We pass no history to ensure "Blind" context
        async for event in architect.run(
            question=user_request,
            context_chunks=context_chunks,
            history=[],
            llm_provider=self.llm_provider,
            tool_service=tool_service,
            tools=tools,
            db=self.db,
            user_id=user_id,
            sync_workspace_id=self.sync_workspace_id,
            session_id=None, # Evaluation shouldn't append to session Message table
            feature_name="agent_harness",
            session_override=system_prompt
        ):
            if event["type"] == "content":
                final_answer += event["content"]
            elif event["type"] == "error":
                logger.error(f"[HarnessEvaluator] Sub-evaluator fault: {event['content']}")
        
        import re
        score = 0
        score_match = re.search(r"FINAL_SCORE:\s*(\d+)", final_answer)
        if score_match:
            try: score = int(score_match.group(1))
            except: score = 0
        
        return {
            "score": score,
            "justification": final_answer
        }

    async def generate_compaction_summary(self, initial_prompt: str, history_log: List[Dict[str, Any]]) -> str:
        """Micro-compaction strategy: Distills multiple failed rework attempts into a high-density directive."""
        if not self.llm_provider or not history_log:
            return "COMPACTED DIRECTIVE: Resolve all remaining implementation gaps identified in previous rounds."

        failure_path = ""
        for i, entry in enumerate(history_log):
            if entry.get("type") == "attempt":
                failure_path += f"Attempt {entry.get('round', i+1)} (Score: {entry.get('score', 0)}): {entry.get('reason', 'Unknown failure')}\n"

        system_prompt = """You are a Quality Control Compactor.
You have analyzed 2+ failed attempts to solve a task.
Your goal is to distill all previous critiques, failures, and delta-analysis reports into a SINGLE, high-density 'Compacted Directive'.
Remove all repetitive context, filler, and conversational markers. Focus only on the 'Critical Delta' that remains unfixed.

Format exactly as:
# COMPACTED DIRECTIVE
[Dense listing of required fixes]"""

        user_prompt = f"Original Mission: {initial_prompt}\n\nFailure History:\n{failure_path}\n\nGenerate the Compacted Directive now."

        try:
            messages = [
                {"role": "system", "content": system_prompt},
                {"role": "user", "content": user_prompt}
            ]
            prediction = await self.llm_provider.acompletion(messages=messages, stream=False)
            return prediction.choices[0].message.content
        except Exception as e:
            logger.error(f"[HarnessEvaluator] Compaction fault: {e}")
            return "# COMPACTED DIRECTIVE\n- Critical implementation failure. Perform deep audit and stabilize core logic."

    async def log_round(self, round_num: int, score: int, reason: str, feedback: str = "", sub_events: List[Dict] = None, duration: float = 0):
        """Append-only record-keeping in history.log for a full attempt round."""
        if not self.assistant: return
        try:
            cmd_res = self.assistant.dispatch_single(
                self.mesh_node_id, 
                "cat .cortex/history.log", 
                session_id=self.sync_workspace_id,
                timeout=5
            )
            
            history = []
            if cmd_res.get("status") == "SUCCESS":
                try:
                    history = json.loads(cmd_res.get("stdout", "[]"))
                except Exception as je:
                    logger.warning(f"[HarnessEvaluator] history.log corruption detected, resetting: {je}")
                    history = []
            
            history.append({
                "type": "attempt",
                "round": round_num,
                "score": score,
                "reason": reason,
                "feedback": feedback,
                "duration": round(duration, 2),
                "timestamp": time.time(),
                "sub_events": sub_events or []
            })
            
            self.assistant.write(
                self.mesh_node_id, 
                ".cortex/history.log", 
                json.dumps(history, indent=2), 
                session_id=self.sync_workspace_id
            )
        except Exception as e:
            logger.error(f"[HarnessEvaluator] Critical fault during log_round: {e}")
