"""Reviewate Workflow

Orchestrates the code review workflow using specialized agents.
"""

from __future__ import annotations

import asyncio
import time
from collections.abc import Callable
from typing import Annotated

from pydantic import BaseModel, Field

from ..adaptors.issue import Issue, IssueHandler
from ..adaptors.repository import RepositoryHandler
from ..agents import (
    DuplicateAgent,
    FactCheckerAgent,
    IssueExplorerAgent,
    OutputParserAgent,
    SimpleReviewAgent,
    StyleAgent,
    SynthesizerAgent,
    TokenUsage,
    TriageAgent,
)
from ..config import Config, Platform
from ..errors import PostingError
from ..explorer import CodeExplorer
from ..schema import (
    GitHubReviewComment,
    GitLabReviewComment,
    MergeRequest,
    ReviewOutput,
)
from .context import ImplicitGuidelineInfo, LinkedRepoInfo, PipelineContext
from .logging import WorkflowLogger

logger = WorkflowLogger("review")

# Type alias for platform review types
PlatformReviewType = GitHubReviewComment | GitLabReviewComment


class WorkflowResult(BaseModel):
    """Final result from the workflow"""

    merge_request: Annotated[MergeRequest, Field(description="The merge request that was reviewed")]
    issues_found: Annotated[list[Issue], Field(description="List of linked issues that were found")]
    synthesized_review: Annotated[
        dict, Field(description="Final synthesized review (platform-specific format)")
    ]
    total_usage: Annotated[TokenUsage, Field(description="Total token usage across all agents")]
    per_agent_usage: Annotated[
        dict[str, dict], Field(default_factory=dict, description="Token usage per agent")
    ]
    review_comments: Annotated[
        list[str], Field(default_factory=list, description="Review comment bodies for scoring")
    ]
    review_objects: Annotated[
        list[dict],
        Field(default_factory=list, description="Full review objects with path/line/body"),
    ]


class ReviewWorkflow:
    """Orchestrates the code review workflow with ensemble agents.

    Pipeline:
    1. Triage - determines reasoning effort
    2. Issue Explorer - finds linked issues
    3. Review Agents (2 parallel instances with tools) - find potential bugs
    4. Synthesizer - merges and deduplicates reviews
    5. Duplicate Agent - dedup against existing human comments (if repository provided)
    6. Fact Checker - verifies claims against code with tools
    7. Style Agent - makes reviews concise and actionable

    Usage:
        workflow = ReviewWorkflow(config)

        # Pure review (no posting):
        result = await workflow.review(merge_request, platform, code_explorer)

        # Review + post:
        result = await workflow.run(merge_request, platform, code_explorer, repository)
    """

    def __init__(self, config: Config):
        """Initialize the workflow with agent configuration.

        Args:
            config: Configuration instance for creating agents
        """
        self.config = config

        # Initialize agents
        self.output_parser = OutputParserAgent(config=config.create_agent_config("output_parser"))
        self.triage = TriageAgent(config=config.create_agent_config("triage_agent"))
        self.issue_explorer = IssueExplorerAgent(
            config=config.create_agent_config("issue_explorer")
        )

        # Ensemble review agents (parallel instances of same generic prompt)
        review_config = config.create_agent_config("review_agent")
        self.num_reviewers = 2
        self.review_agents = [
            SimpleReviewAgent(config=review_config, instance_id=i)
            for i in range(self.num_reviewers)
        ]

        # Synthesizer to merge results
        self.synthesizer = SynthesizerAgent(config=config.create_agent_config("output_parser"))

        self.deduplicate = DuplicateAgent(config=config.create_agent_config("duplicate_detector"))
        self.fact_checker = FactCheckerAgent(config=config.create_agent_config("fact_checker"))
        self.style = StyleAgent(config=config.create_agent_config("style_agent"))

    async def review(
        self,
        merge_request: MergeRequest,
        platform: Platform,
        code_explorer: CodeExplorer,
        user_guidelines: str = "",
        issue_handler: IssueHandler | None = None,
        repository: RepositoryHandler | None = None,
        debug: bool = False,
        linked_repos: list[LinkedRepoInfo] | None = None,
        implicit_guidelines: list[ImplicitGuidelineInfo] | None = None,
        team_guidelines: str | None = None,
        on_status: Callable[[str], None] | None = None,
        on_step_done: Callable[[str], None] | None = None,
    ) -> WorkflowResult:
        """Run the review pipeline. Returns results without posting.

        Args:
            merge_request: The merge request to review (with diffs)
            platform: Platform type (GitHub or GitLab)
            code_explorer: CodeExplorer or MultiCodeExplorer for code exploration (required)
            user_guidelines: User guidelines from CLAUDE.md/AGENTS.md (optional)
            issue_handler: For fetching linked issues (optional, skips if None)
            repository: For deduplication against existing comments (optional, skips dedup if None)
            debug: Enable debug mode for verbose output
            linked_repos: List of linked repository info for prompt rendering (optional)
            implicit_guidelines: Learned guidelines from user feedback (optional)
            team_guidelines: Natural language team guidelines from feedback summarization (optional)

        Returns:
            WorkflowResult with reviews and metrics
        """
        start_time = time.time()
        logger.info(f"Starting review workflow for {merge_request.repo}#{merge_request.id}")

        try:
            # Initialize pipeline context
            ctx = PipelineContext(
                repo=merge_request.repo,
                merge_id=merge_request.id,
                platform=platform,
                debug=debug,
                response_model=self._get_response_model(platform),
            )

            # Set status callbacks and provided data
            ctx.on_status = on_status
            ctx.on_step_done = on_step_done
            ctx.merge_request = merge_request
            ctx.user_guidelines = user_guidelines
            ctx.code_explorer = code_explorer
            ctx.linked_repos = linked_repos or []
            ctx.implicit_guidelines = implicit_guidelines or []
            ctx.team_guidelines = team_guidelines
            ctx.platform_output_format = self._get_platform_output_format(platform)

            # Run agents
            if ctx.on_status:
                ctx.on_status("Triaging...")
            await self.triage.run(ctx)
            if ctx.on_step_done:
                ctx.on_step_done(ctx.reasoning_effort or "high")

            if ctx.on_status:
                ctx.on_status("Exploring linked issues...")
            await self.issue_explorer.run(ctx, issue_handler, self.output_parser)
            if ctx.on_step_done:
                n = len(ctx.issues)
                ctx.on_step_done(f"{n} found" if n else "none")

            if ctx.on_status:
                ctx.on_status(f"Reviewing ({self.num_reviewers} agents)...")
            await self._run_ensemble_agents(ctx)
            if ctx.on_step_done:
                ctx.on_step_done(f"{len(ctx.reviews)} issues")

            # Deduplication requires repository to fetch existing comments
            if repository is not None:
                before_dedup = len(ctx.reviews)
                if ctx.on_status:
                    ctx.on_status("Deduplicating...")
                await self.deduplicate.run(ctx, repository, self.output_parser)
                if ctx.on_step_done:
                    ctx.on_step_done(f"{before_dedup} \u2192 {len(ctx.reviews)}")

            before_fc = len(ctx.reviews)
            if ctx.on_status:
                ctx.on_status("Fact-checking...")
            await self.fact_checker.run(ctx)
            if ctx.on_step_done:
                ctx.on_step_done(f"{before_fc} \u2192 {len(ctx.reviews)}")

            if ctx.on_status:
                ctx.on_status("Styling...")
            await self.style.run(ctx, self.output_parser)

            total_duration = time.time() - start_time
            logger.info(
                f"Workflow completed in {total_duration:.2f}s: "
                f"{len(ctx.issues)} issues, {len(ctx.reviews)} reviews, "
                f"{ctx.total_usage.total_tokens} tokens"
            )

            return self._build_result(ctx)

        except Exception as e:
            logger.error("Workflow failed", type=type(e).__name__, message=str(e))
            raise

    async def post(
        self,
        result: WorkflowResult,
        platform: Platform,
        repository: RepositoryHandler,
    ) -> None:
        """Post review results to platform.

        Args:
            result: WorkflowResult from review()
            platform: Platform type (GitHub or GitLab)
            repository: Repository handler for posting
        """
        response_model = self._get_response_model(platform)
        final_review = response_model(reviews=result.synthesized_review.get("reviews", []))

        if final_review.reviews:
            logger.info(f"Posting {len(final_review.reviews)} reviews")
            successful, failed = await repository.post_review(
                merge_request=result.merge_request,
                reviews=list(final_review.reviews),
            )
            if successful == 0 and failed > 0:
                raise PostingError(f"All {failed} review comments failed to post")
            if failed > 0:
                logger.warning(
                    f"Posted {successful}/{successful + failed} comments ({failed} failed)"
                )
            # Update result to only reflect successfully posted comments count
            result.review_comments = result.review_comments[:successful]
        else:
            logger.info("No issues found, posting LGTM")
            await repository.post_comment(
                repo=result.merge_request.repo,
                merge_id=result.merge_request.id,
                content="LGTM, you still need approval from a human. Good job 🍾 👏",
            )

    async def run(
        self,
        merge_request: MergeRequest,
        platform: Platform,
        code_explorer: CodeExplorer,
        repository: RepositoryHandler,
        issue_handler: IssueHandler | None = None,
        user_guidelines: str = "",
        debug: bool = False,
        linked_repos: list[LinkedRepoInfo] | None = None,
        implicit_guidelines: list[ImplicitGuidelineInfo] | None = None,
        team_guidelines: str | None = None,
        on_status: Callable[[str], None] | None = None,
        on_step_done: Callable[[str], None] | None = None,
    ) -> WorkflowResult:
        """review() + post() convenience method.

        Args:
            merge_request: The merge request to review
            platform: Platform type (GitHub or GitLab)
            code_explorer: CodeExplorer or MultiCodeExplorer for code exploration
            repository: Repository handler for dedup + posting
            issue_handler: For fetching linked issues (optional)
            user_guidelines: User guidelines (optional)
            debug: Enable debug mode
            linked_repos: Linked repos for cross-repo exploration (optional)
            implicit_guidelines: Learned guidelines from user feedback (optional)
            team_guidelines: Natural language team guidelines (optional)

        Returns:
            WorkflowResult with reviews and metrics
        """
        result = await self.review(
            merge_request=merge_request,
            platform=platform,
            code_explorer=code_explorer,
            user_guidelines=user_guidelines,
            issue_handler=issue_handler,
            repository=repository,
            debug=debug,
            linked_repos=linked_repos,
            implicit_guidelines=implicit_guidelines,
            team_guidelines=team_guidelines,
            on_status=on_status,
            on_step_done=on_step_done,
        )
        if on_status:
            on_status("Posting review...")
        await self.post(result, platform, repository)
        if on_step_done:
            n = len(result.review_comments)
            on_step_done(f"{n} comments" if n else "LGTM")
        return result

    async def _run_ensemble_agents(self, ctx: PipelineContext) -> None:
        """Run ensemble of generic review agents in parallel and synthesize results."""
        if ctx.merge_request is None:
            raise ValueError("merge_request must be set before running review agents")
        if ctx.response_model is None:
            raise ValueError("response_model must be set before running review agents")
        if ctx.code_explorer is None:
            raise ValueError("code_explorer must be set before running review agents")

        logger.info(f"Running {self.num_reviewers} review agents in parallel (ensemble mode)")

        # Apply reasoning effort to all agents
        for agent in self.review_agents:
            agent.base.reasoning_effort = ctx.reasoning_effort

        # Capture validated values for closure (mypy needs this)
        merge_request = ctx.merge_request
        response_model = ctx.response_model
        code_explorer = ctx.code_explorer

        # Run all agents in parallel
        async def run_agent(agent: SimpleReviewAgent) -> tuple[int, list, TokenUsage]:
            result = await agent.analyze(
                merge_info=merge_request,
                diffs=merge_request.diffs,
                output_parser=self.output_parser,
                response_model=response_model,
                issues=ctx.issues,
                user_guidelines=ctx.user_guidelines,
                linked_repos=ctx.linked_repos,
                team_guidelines=ctx.team_guidelines,
                code_explorer=code_explorer,
                platform_output_format=ctx.platform_output_format,
            )
            return agent.instance_id, list(result.output.reviews), result.metadata

        tasks = [run_agent(agent) for agent in self.review_agents]
        results = await asyncio.gather(*tasks)

        # Collect results by agent
        agent_reviews: dict[str, list] = {}
        for instance_id, reviews, usage in results:
            agent_name = f"Reviewer-{instance_id}"
            agent_reviews[agent_name] = [
                r.model_dump() if hasattr(r, "model_dump") else r for r in reviews
            ]
            ctx.track(f"reviewer_{instance_id}", usage)
            logger.info(f"{agent_name} found {len(reviews)} issues")

        # Synthesize results (merge and deduplicate)
        total_raw = sum(len(r) for r in agent_reviews.values())
        if total_raw > 0:
            logger.info(
                f"Synthesizing {total_raw} total issues from {self.num_reviewers} reviewers"
            )
            synth_result = await self.synthesizer.synthesize(
                agent_reviews=agent_reviews,
                merge_info=merge_request,
                output_parser=self.output_parser,
                response_model=response_model,
            )
            ctx.reviews = list(synth_result.output.reviews)
            ctx.track("synthesizer", synth_result.metadata)
            logger.info(f"Synthesized to {len(ctx.reviews)} unique issues")
        else:
            ctx.reviews = []
            logger.info("No issues found by any reviewer")

    # Helper methods

    def _get_response_model(
        self, platform: Platform
    ) -> type[ReviewOutput[GitHubReviewComment]] | type[ReviewOutput[GitLabReviewComment]]:
        match platform:
            case Platform.GITHUB:
                return ReviewOutput[GitHubReviewComment]
            case Platform.GITLAB:
                return ReviewOutput[GitLabReviewComment]

    @staticmethod
    def _get_platform_output_format(platform: Platform) -> str:
        from ..adaptors.repository.github.handler import GitHubRepositoryHandler
        from ..adaptors.repository.gitlab.handler import GitLabRepositoryHandler

        match platform:
            case Platform.GITHUB:
                return GitHubRepositoryHandler.output_format()
            case Platform.GITLAB:
                return GitLabRepositoryHandler.output_format()

    def _build_result(self, ctx: PipelineContext) -> WorkflowResult:
        assert ctx.response_model is not None
        assert ctx.merge_request is not None

        final_review = ctx.response_model(reviews=ctx.reviews)
        review_comments = [r.body for r in final_review.reviews if hasattr(r, "body") and r.body]
        # Full review objects for fact checker dataset generation
        review_objects = [
            r.model_dump() for r in final_review.reviews if hasattr(r, "body") and r.body
        ]
        return WorkflowResult(
            merge_request=ctx.merge_request,
            issues_found=ctx.issues,
            synthesized_review=final_review.model_dump(),
            total_usage=ctx.total_usage,
            per_agent_usage=ctx.per_agent_usage,
            review_comments=review_comments,
            review_objects=review_objects,
        )
