"""IterationAgent — applies improvements based on review feedback."""

from __future__ import annotations

from pydantic import BaseModel, Field

from pptx_mcp.agents.base import BaseAgent
from pptx_mcp.models.review import ReviewIssue, ReviewResult, Severity, ReviewCategory
from pptx_mcp.models.slides import (
    ColumnContent,
    PresentationDefinition,
    SlideDefinition,
    SlideType,
)


class IterationInput(BaseModel):
    """Input for the IterationAgent."""

    definition: PresentationDefinition
    review: ReviewResult
    auto_fix: bool = Field(
        default=True,
        description="Automatically apply fixes for deterministic issues",
    )
    max_iterations: int = Field(
        default=1,
        ge=1,
        le=5,
        description="Number of fix passes to attempt",
    )


class IterationOutput(BaseModel):
    """Output from the IterationAgent."""

    definition: PresentationDefinition
    fixes_applied: list[str] = Field(default_factory=list)
    remaining_issues: list[ReviewIssue] = Field(default_factory=list)
    iterations_run: int = 0


class IterationAgent(BaseAgent[IterationInput, IterationOutput]):
    """Applies targeted fixes based on review feedback.

    This agent processes ReviewResult issues and applies deterministic
    fixes where possible. Issues that require creative judgment (e.g.,
    "improve narrative flow") are flagged as remaining issues for the
    LLM orchestrator to handle.
    """

    @property
    def name(self) -> str:
        return "iterator"

    @property
    def description(self) -> str:
        return "Applies changes to presentations based on review feedback."

    def run(self, input_data: IterationInput) -> IterationOutput:
        """Apply fixes to the presentation.

        Args:
            input_data: Presentation definition with review feedback.

        Returns:
            Updated definition with fixes applied and remaining issues.
        """
        definition = input_data.definition.model_copy(deep=True)
        all_fixes: list[str] = []
        remaining: list[ReviewIssue] = []

        for iteration in range(input_data.max_iterations):
            fixes, still_remaining = self._apply_fixes(
                definition, input_data.review.issues
            )
            all_fixes.extend(fixes)
            remaining = still_remaining

            if not fixes:
                break

        self.logger.info(
            "Iteration complete: %d fixes applied, %d remaining",
            len(all_fixes),
            len(remaining),
        )

        return IterationOutput(
            definition=definition,
            fixes_applied=all_fixes,
            remaining_issues=remaining,
            iterations_run=min(input_data.max_iterations, len(all_fixes) + 1),
        )

    def _apply_fixes(
        self,
        definition: PresentationDefinition,
        issues: list[ReviewIssue],
    ) -> tuple[list[str], list[ReviewIssue]]:
        """Attempt to fix issues in the definition.

        Processes slide-removal fixes last (in reverse index order) to avoid
        stale-index corruption when earlier removals shift subsequent indices.

        Returns:
            Tuple of (fixes applied, remaining unfixed issues).
        """
        fixes: list[str] = []
        remaining: list[ReviewIssue] = []
        pending_removals: list[tuple[int, ReviewIssue]] = []

        for issue in issues:
            # Defer empty-slide removals to process in reverse order
            if (
                issue.slide_index is not None
                and "empty" in issue.message.lower()
                and issue.severity == Severity.ERROR
            ):
                pending_removals.append((issue.slide_index, issue))
                continue

            fixed = self._try_fix(definition, issue)
            if fixed:
                fixes.append(fixed)
            else:
                remaining.append(issue)

        # Process removals in reverse index order to keep indices stable
        pending_removals.sort(key=lambda pair: pair[0], reverse=True)
        for idx, issue in pending_removals:
            if idx < len(definition.slides):
                definition.slides.pop(idx)
                fixes.append(f"Slide {idx}: Removed empty slide.")
            else:
                remaining.append(issue)

        return fixes, remaining

    def _try_fix(
        self,
        definition: PresentationDefinition,
        issue: ReviewIssue,
    ) -> str | None:
        """Try to apply a single fix. Returns description if fixed, None otherwise."""
        if issue.slide_index is not None:
            if issue.slide_index >= len(definition.slides):
                return None
            slide = definition.slides[issue.slide_index]
        else:
            slide = None

        # Content fixes
        if issue.category == ReviewCategory.CONTENT:
            return self._fix_content_issue(definition, slide, issue)

        # Layout fixes
        if issue.category == ReviewCategory.LAYOUT:
            return self._fix_layout_issue(slide, issue)

        # Whitespace/balance fixes
        if issue.category == ReviewCategory.WHITESPACE:
            return self._fix_balance_issue(slide, issue)

        return None

    def _fix_content_issue(
        self,
        definition: PresentationDefinition,
        slide: SlideDefinition | None,
        issue: ReviewIssue,
    ) -> str | None:
        """Fix content-related issues."""
        if slide is None:
            return None

        idx = issue.slide_index

        # Too many bullets: split into two-column
        if "bullets" in issue.message.lower() and len(slide.bullets) > 6:
            mid = len(slide.bullets) // 2
            slide.columns = [
                ColumnContent(bullets=slide.bullets[:mid]),
                ColumnContent(bullets=slide.bullets[mid:]),
            ]
            slide.bullets = []
            slide.slide_type = SlideType.TWO_COLUMN
            return f"Slide {idx}: Split overloaded bullets into two-column layout."

        return None

    def _fix_layout_issue(
        self,
        slide: SlideDefinition | None,
        issue: ReviewIssue,
    ) -> str | None:
        """Fix layout-related issues."""
        if slide is None:
            return None

        idx = issue.slide_index

        # Missing title
        if "missing a title" in issue.message.lower():
            if slide.slide_type == SlideType.CONTENT and slide.bullets:
                # Use first bullet as title
                slide.title = slide.bullets[0]
                slide.bullets = slide.bullets[1:]
                return f"Slide {idx}: Promoted first bullet to title."
            elif slide.body:
                words = slide.body.split()
                slide.title = " ".join(words[:6])
                return f"Slide {idx}: Generated title from body text."

        return None

    def _fix_balance_issue(
        self,
        slide: SlideDefinition | None,
        issue: ReviewIssue,
    ) -> str | None:
        """Fix balance and whitespace issues."""
        if slide is None:
            return None

        idx = issue.slide_index

        # Unbalanced columns
        if "unbalanced" in issue.message.lower() and slide.columns:
            all_bullets: list[str] = []
            for col in slide.columns:
                all_bullets.extend(col.bullets)

            if all_bullets and len(slide.columns) >= 2:
                per_col = len(all_bullets) // len(slide.columns)
                remainder = len(all_bullets) % len(slide.columns)
                start = 0
                for i, col in enumerate(slide.columns):
                    count = per_col + (1 if i < remainder else 0)
                    col.bullets = all_bullets[start : start + count]
                    start += count
                return f"Slide {idx}: Rebalanced {len(all_bullets)} items across {len(slide.columns)} columns."

        return None
