"""
PR iteration agent for deterministic two-phase execution.

This agent wraps the /iterate-pr workflow with Claude Agent SDK,
ensuring:
1. Phase 1 (classification) is always read-only
2. Phase 2 (execution) only runs after approval
3. Deterministic behavior regardless of invocation method
"""

import json
import os
import subprocess
from dataclasses import dataclass, field
from pathlib import Path
from subprocess import TimeoutExpired
from typing import Any

from persuade.agents.base import AgentPhaseResult, TwoPhaseAgent
from persuade.logging import get_logger

# Optional Pydantic import for state file validation
try:
    from pydantic import BaseModel, Field, ValidationError

    PYDANTIC_AVAILABLE = True
except ImportError:
    PYDANTIC_AVAILABLE = False

logger = get_logger()

# Constants (configurable via environment variables)
DEFAULT_WORKFLOW_TIMEOUT_SECONDS = int(
    os.getenv("PERSUADE_WORKFLOW_TIMEOUT", "60")
)  # Timeout for workflow script execution
MAX_PREVIEW_LENGTH = 50  # Characters for feedback body preview in plan summary
MAX_PREVIEW_ITEMS = 3  # Max items to show per category in plan summary

# Optional Claude Agent SDK import
try:
    from claude_agent_sdk import ClaudeAgentOptions, query

    AGENT_SDK_AVAILABLE = True
except ImportError:
    AGENT_SDK_AVAILABLE = False


@dataclass
class FeedbackItem:
    """Represents a single feedback item from PR reviews."""

    id: str
    item_type: str  # review, review_comment, issue_comment
    author: str
    author_type: str  # human, bot
    body: str
    url: str
    status: str  # unaddressed, addressed, skipped
    priority: str | None = None  # CRITICAL, HIGH, MEDIUM, LOW, NITPICK
    path: str | None = None  # File path for review comments
    line: int | None = None  # Line number for review comments


# State file validation model (only if Pydantic available)
if PYDANTIC_AVAILABLE:

    class FeedbackItemModel(BaseModel):
        """Pydantic model for feedback item validation."""

        id: str
        item_type: str
        author: str
        author_type: str = "human"
        body: str
        url: str
        status: str = "unaddressed"
        priority: str | None = None
        path: str | None = None
        line: int | None = None

    class StateFileModel(BaseModel):
        """Schema for PR state file validation."""

        pr_number: int = Field(gt=0)
        feedback_items: list[FeedbackItemModel] = Field(default_factory=list)
        last_updated: str | None = None


@dataclass
class PRIterationPlan:
    """Plan produced by the classification phase."""

    pr_number: int
    feedback_items: list[FeedbackItem] = field(default_factory=list)
    ci_failures: list[str] = field(default_factory=list)
    iteration_required: bool = False
    summary: str = ""
    warnings: list[str] = field(default_factory=list)


class IteratePRAgent(TwoPhaseAgent):
    """Deterministic PR iteration agent.

    This agent provides two-phase execution for PR iteration:
    1. Plan: Fetch and classify PR feedback (read-only)
    2. Execute: Address feedback items (with Claude Agent SDK)

    The plan phase uses the existing check-pr-reviews.sh script and
    state file infrastructure. The execute phase uses Claude Agent SDK
    with permission_mode="acceptEdits" for deterministic code changes.
    """

    def __init__(
        self,
        pr_number: int,
        project_dir: Path | None = None,
        workflow_timeout: int = DEFAULT_WORKFLOW_TIMEOUT_SECONDS,
        strict: bool = False,
    ):
        """Initialize the agent.

        Args:
            pr_number: The PR number to iterate on
            project_dir: Project directory (defaults to current working directory)
            workflow_timeout: Timeout in seconds for workflow script execution
            strict: If True, raise errors on validation failures (fail-fast mode)
        """
        self.pr_number = pr_number
        self.project_dir = project_dir or Path.cwd()
        self.workflow_timeout = workflow_timeout
        self.strict = strict
        self.state_dir = self.project_dir / ".claude" / "state"
        self.state_file = self.state_dir / f"pr_{pr_number}_monitor.json"
        self.scripts_dir = self.project_dir / ".claude" / "scripts"

    @classmethod
    def is_executable(cls) -> bool:
        """Check if agent can execute (requires Claude Agent SDK).

        The plan phase always works, but execution requires the Claude Agent SDK.
        Use this method to check availability before attempting execution.

        Returns:
            True if Claude Agent SDK is installed and execution is possible.
        """
        return AGENT_SDK_AVAILABLE

    async def plan(self, **kwargs: Any) -> AgentPhaseResult:
        """Phase 1: Fetch and classify PR feedback (read-only).

        This phase:
        1. Runs check-pr-reviews.sh to fetch latest feedback
        2. Reads the state file for feedback items
        3. Classifies items by priority and type
        4. Returns a plan for execution

        Returns:
            AgentPhaseResult with classification data
        """
        try:
            # Step 1: Run the workflow script to fetch feedback
            iteration_plan = self._fetch_feedback()

            if not iteration_plan.iteration_required:
                return AgentPhaseResult(
                    success=True,
                    data={"plan": iteration_plan},
                    messages=[
                        "No iteration needed - PR is ready!",
                        "  - All CI checks passing",
                        "  - No pending reviews or comments",
                    ],
                )

            # Step 2: Build human-readable summary
            messages = self._build_plan_summary(iteration_plan)

            return AgentPhaseResult(
                success=True,
                data={"plan": iteration_plan},
                messages=messages,
            )

        except subprocess.CalledProcessError as e:
            # Include stdout/stderr for debugging
            details = f"stdout={e.stdout!r}, stderr={e.stderr!r}" if e.stdout or e.stderr else ""
            return AgentPhaseResult(
                success=False,
                error=f"Failed to fetch PR feedback: {e}. {details}".strip(),
            )
        except FileNotFoundError as e:
            return AgentPhaseResult(
                success=False,
                error=f"Required script not found: {e}",
            )
        except json.JSONDecodeError as e:
            return AgentPhaseResult(
                success=False,
                error=f"Failed to parse state file: {e}",
            )
        except TimeoutExpired as e:
            # Include any partial output for debugging
            details = f"stdout={e.stdout!r}, stderr={e.stderr!r}" if e.stdout or e.stderr else ""
            msg = f"Workflow script timed out after {self.workflow_timeout}s. {details}".strip()
            return AgentPhaseResult(success=False, error=msg)

    async def execute(self, plan_result: AgentPhaseResult, **kwargs: Any) -> AgentPhaseResult:
        """Phase 2: Execute approved changes.

        This phase uses Claude Agent SDK with permission_mode="acceptEdits"
        to address the feedback items identified in the plan phase.

        Args:
            plan_result: The result from the plan phase

        Returns:
            AgentPhaseResult with execution summary
        """
        if not AGENT_SDK_AVAILABLE:
            return AgentPhaseResult(
                success=False,
                error=(
                    "Claude Agent SDK not installed. Install with: pip install persuade[agents]"
                ),
            )

        iteration_plan = plan_result.data.get("plan")
        if not iteration_plan or not isinstance(iteration_plan, PRIterationPlan):
            return AgentPhaseResult(
                success=False,
                error="No plan data found in plan_result",
            )

        if not iteration_plan.iteration_required:
            return AgentPhaseResult(
                success=True,
                messages=["No changes needed - PR is ready!"],
            )

        # Build execution prompt from the plan
        prompt = self._build_execution_prompt(iteration_plan)

        try:
            # Execute with Claude Agent SDK
            # Collect string representations of agent messages
            messages: list[str] = []
            async for msg in query(
                prompt=prompt,
                options=ClaudeAgentOptions(
                    permission_mode="acceptEdits",
                    setting_sources=["project"],
                ),
            ):
                # Collect execution progress
                if hasattr(msg, "content"):
                    messages.append(str(msg.content))

            return AgentPhaseResult(
                success=True,
                data={"execution_messages": messages},
                messages=[
                    f"Addressed {len(iteration_plan.feedback_items)} feedback items",
                    "Changes have been made to the codebase",
                    "Run 'git diff' to review changes",
                ],
            )

        except (TimeoutError, ConnectionError, OSError) as e:
            # Handle expected network/connection issues from SDK
            # Let unexpected errors (AttributeError, TypeError, etc.) bubble up
            return AgentPhaseResult(
                success=False,
                error=f"Execution failed: {e}",
            )

    def _fetch_feedback(self) -> PRIterationPlan:
        """Fetch PR feedback using the workflow script.

        Returns:
            PRIterationPlan with feedback items and CI status
        """
        # Run iterate-pr-workflow.sh to update state
        workflow_script = self.scripts_dir / "iterate-pr-workflow.sh"
        if workflow_script.exists():
            result = subprocess.run(
                [str(workflow_script), str(self.pr_number)],
                cwd=self.project_dir,
                capture_output=True,
                text=True,
                timeout=self.workflow_timeout,
            )
            # Exit code convention is intentionally inverted from standard Unix:
            # - 0 means "iteration needed" (caller should continue processing)
            # - 1 means "no iteration needed" (caller can stop)
            # This allows shell scripts to use: if ./workflow.sh; then iterate; fi
            iteration_required = result.returncode == 0
        else:
            # Fall back to just reading state file
            iteration_required = True

        # Read state file for detailed feedback
        if not self.state_file.exists():
            return PRIterationPlan(
                pr_number=self.pr_number,
                iteration_required=False,
                summary="No state file found - run workflow script first",
            )

        # Load state file
        try:
            with open(self.state_file) as f:
                state = json.load(f)
        except json.JSONDecodeError as e:
            logger.warning(f"Invalid JSON in state file: {e}")
            return PRIterationPlan(
                pr_number=self.pr_number,
                iteration_required=False,
                summary=f"Invalid JSON in state file: {e}",
            )

        # Validate schema if Pydantic is available
        if PYDANTIC_AVAILABLE:
            try:
                StateFileModel.model_validate(state)
            except ValidationError as e:
                if self.strict:
                    # Fail fast per Engineering Standards
                    raise ValueError(
                        f"State file schema validation failed: {e}. "
                        f"Fix the state file at {self.state_file} or regenerate it."
                    ) from e
                # DESIGN NOTE: In lenient mode (strict=False), validation failures are
                # treated as "no iteration needed" for resilience. The agent continues
                # gracefully even with malformed state files. Use strict=True for
                # fail-fast behavior.
                logger.warning(f"State file schema validation failed: {e}")
                return PRIterationPlan(
                    pr_number=self.pr_number,
                    iteration_required=False,
                    summary=f"State file validation failed: {e}",
                    warnings=[f"Schema validation failed: {e}"],
                )

        # Parse feedback items
        feedback_items: list[FeedbackItem] = []
        for item in state.get("feedback_items", []):
            if item.get("status") == "unaddressed":
                feedback_items.append(
                    FeedbackItem(
                        id=item.get("id", ""),
                        item_type=item.get("item_type", ""),
                        author=item.get("author", ""),
                        author_type=item.get("author_type", "human"),
                        body=item.get("body", ""),
                        url=item.get("url", ""),
                        status=item.get("status", "unaddressed"),
                        priority=item.get("priority"),
                        path=item.get("path"),
                        line=item.get("line"),
                    )
                )

        # Parse CI failures from workflow output (if available)
        ci_failures: list[str] = []
        # CI failures would be parsed from the workflow script output

        return PRIterationPlan(
            pr_number=self.pr_number,
            feedback_items=feedback_items,
            ci_failures=ci_failures,
            iteration_required=iteration_required and len(feedback_items) > 0,
            summary=f"Found {len(feedback_items)} unaddressed feedback items",
        )

    def _build_plan_summary(self, plan: PRIterationPlan) -> list[str]:
        """Build human-readable summary of the plan.

        Args:
            plan: The iteration plan

        Returns:
            List of summary messages
        """
        messages = [
            f"PR #{plan.pr_number} requires iteration",
            f"Found {len(plan.feedback_items)} unaddressed feedback items:",
            "",
        ]

        # Group by type
        by_type: dict[str, list[FeedbackItem]] = {}
        for item in plan.feedback_items:
            by_type.setdefault(item.item_type, []).append(item)

        for item_type, items in by_type.items():
            type_label = item_type.replace("_", " ").title()
            messages.append(f"  {type_label}s: {len(items)}")
            for item in items[:MAX_PREVIEW_ITEMS]:
                preview = (
                    item.body[:MAX_PREVIEW_LENGTH] + "..."
                    if len(item.body) > MAX_PREVIEW_LENGTH
                    else item.body
                )
                author_label = "[BOT]" if item.author_type == "bot" else f"@{item.author}"
                messages.append(f"    - {author_label}: {preview}")
            if len(items) > MAX_PREVIEW_ITEMS:
                messages.append(f"    ... and {len(items) - MAX_PREVIEW_ITEMS} more")

        if plan.ci_failures:
            messages.append("")
            messages.append(f"  CI Failures: {len(plan.ci_failures)}")
            for failure in plan.ci_failures[:MAX_PREVIEW_ITEMS]:
                messages.append(f"    - {failure}")

        if plan.warnings:
            messages.append("")
            messages.append("[yellow]Warnings:[/yellow]")
            for warning in plan.warnings:
                messages.append(f"  ⚠️  {warning}")

        return messages

    def _build_execution_prompt(self, plan: PRIterationPlan) -> str:
        """Build the prompt for Claude Agent SDK execution.

        Args:
            plan: The iteration plan

        Returns:
            Prompt string for Claude
        """
        # Build a structured prompt that references the guidelines
        items_text = ""
        for item in plan.feedback_items:
            location = f" at {item.path}:{item.line}" if item.path else ""
            items_text += f"""
### Feedback from {item.author} ({item.author_type}){location}
Type: {item.item_type}
URL: {item.url}

{item.body}
---
"""

        return f"""You are addressing feedback on PR #{plan.pr_number}.

## Guidelines
Follow the feedback classification guidelines in .claude/knowledge/pr-feedback/.
Apply priority rules: CRITICAL > HIGH > MEDIUM > LOW > NITPICK.
Evaluate based on CONTENT not SOURCE (bot feedback may be as important as human feedback).

## Feedback Items to Address
{items_text}

## Instructions
1. Read each feedback item carefully
2. Classify by priority based on content
3. Address items in priority order
4. For each item:
   - Make the necessary code changes
   - Verify the fix is correct
   - Mark as addressed in state file

Start by reading the relevant source files, then make changes.
"""
