"""Duplicate Agent

De-duplicates AI reviews against human discussions.
"""

from __future__ import annotations

import json
import logging
from typing import TYPE_CHECKING, Annotated, Any

from pydantic import BaseModel, ConfigDict, Field

from ..schema import (
    BasicReviewOutput,
    DiscussionItem,
    GitHubReviewComment,
    GitLabReviewComment,
    ReviewOutput,
)
from .base import AgentConfig, AgentResponse, BaseAgentImpl, TokenUsage
from .output_parser import OutputParserAgent
from .utils import build_model_name, format_template

if TYPE_CHECKING:
    from ..adaptors.repository import RepositoryHandler
    from ..workflows.context import PipelineContext

logger = logging.getLogger(__name__)

# Type alias for response model types
type ReviewOutputType = (
    type[ReviewOutput[GitHubReviewComment]]
    | type[ReviewOutput[GitLabReviewComment]]
    | type[ReviewOutput[BasicReviewOutput]]
)

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


class DedupResult(BaseModel):
    """Result from deduplication - just indices to keep."""

    model_config = ConfigDict(extra="forbid")

    keep_indices: Annotated[list[int], Field(description="Indices of AI reviews to keep (0-based)")]


class DuplicateAgent:
    """Agent that de-duplicates AI-generated reviews against existing human discussions"""

    def __init__(
        self,
        config: Annotated[AgentConfig, Field(description="LLM configuration")],
    ):
        """Initialize DuplicateAgent

        Args:
            config: LLM configuration
        """
        self.base = BaseAgentImpl(
            name="Duplicate",
            description="De-duplicates AI-generated reviews against existing human discussions",
            config=config,
        )

    async def deduplicate(
        self,
        human_discussions: list[DiscussionItem],
        ai_reviews: list[PlatformReviewType],
        output_parser: OutputParserAgent,
        response_model: ReviewOutputType,
    ) -> AgentResponse[
        ReviewOutput[GitHubReviewComment]
        | ReviewOutput[GitLabReviewComment]
        | ReviewOutput[BasicReviewOutput]
    ]:
        """Remove duplicate AI reviews that overlap with human discussions

        Args:
            human_discussions: Existing human review discussions
            ai_reviews: AI-generated reviews to filter
            output_parser: Output parser agent for fixing invalid JSON responses
            response_model: Pydantic model class for response

        Returns:
            AgentResponse with parsed output and token usage

        Raises:
            ReviewateError: If deduplication fails
        """
        # If no reviews or no discussions, return as-is
        if not ai_reviews:
            return AgentResponse(
                output=response_model(reviews=[]),
                metadata=TokenUsage(
                    input_tokens=0,
                    output_tokens=0,
                    total_tokens=0,
                    cached_tokens=0,
                    model=build_model_name(self.base.config.provider, self.base.config.model),
                ),
            )

        if not human_discussions:
            # No human discussions to compare against - keep all
            # Cast to Any to work around mypy limitation with union types in generics
            reviews_any: Any = ai_reviews
            return AgentResponse(
                output=response_model(reviews=reviews_any),
                metadata=TokenUsage(
                    input_tokens=0,
                    output_tokens=0,
                    total_tokens=0,
                    cached_tokens=0,
                    model=build_model_name(self.base.config.provider, self.base.config.model),
                ),
            )

        # Build context with indexed AI reviews for comparison
        # Handle different platform review types (GitHub: path/line, GitLab: new_path/new_line)
        indexed_reviews = []
        for i, r in enumerate(ai_reviews):
            review_dict: dict[str, int | str | None] = {"index": i, "body": r.body}
            # GitHub uses path/line
            if isinstance(r, GitHubReviewComment):
                review_dict["path"] = r.path
                review_dict["line"] = r.line
            # GitLab uses new_path/new_line
            elif isinstance(r, GitLabReviewComment):
                review_dict["path"] = r.new_path
                review_dict["line"] = r.new_line
            # BasicReviewOutput has no path/line - only body
            indexed_reviews.append(review_dict)

        ctx = {
            "human_discussions": [d.model_dump() for d in human_discussions],
            "ai_reviews": json.dumps(indexed_reviews, indent=2),
            "review_count": len(ai_reviews),
        }

        # Load system prompt template
        template = self.base.load_prompt("duplicate_agent.txt")

        # Format system prompt
        system_prompt = format_template(self.base.jinja_env, template, ctx)

        # Get output format for DedupResult (indices only)
        output_format = self.base.get_output_format(DedupResult)

        # Call LLM to get indices to keep
        result, usage = await self.base.call_llm(
            system_prompt=system_prompt,
            user_prompt=output_format,
            response_model=DedupResult,
        )

        # Filter reviews by kept indices
        kept_reviews = [ai_reviews[i] for i in result.keep_indices if 0 <= i < len(ai_reviews)]

        # Cast to Any to work around mypy limitation with union types in generics
        kept_reviews_any: Any = kept_reviews
        return AgentResponse(
            output=response_model(reviews=kept_reviews_any),
            metadata=usage,
        )

    async def run(
        self,
        ctx: PipelineContext,
        repository: RepositoryHandler,
        output_parser: OutputParserAgent,
    ) -> None:
        """Execute as a pipeline step.

        Fetches human discussions, deduplicates ctx.reviews.
        """
        if ctx.response_model is None:
            raise ValueError("response_model must be set before running deduplicate")
        if not ctx.reviews:
            logger.info("Deduplicate skipped: no reviews")
            return

        logger.info(f"Starting Deduplicate with {len(ctx.reviews)} reviews")
        before_count = len(ctx.reviews)

        # Fetch existing human discussions
        discussions = await repository.fetch_discussions(ctx.repo, ctx.merge_id)

        result = await self.deduplicate(
            human_discussions=discussions,
            ai_reviews=ctx.reviews,
            output_parser=output_parser,
            response_model=ctx.response_model,
        )

        ctx.reviews = list(result.output.reviews)
        ctx.track("deduplicate", result.metadata)
        logger.info(f"Deduplicate completed: {before_count} -> {len(ctx.reviews)} reviews")

    @classmethod
    def default(cls) -> DuplicateAgent:
        """Create DuplicateAgent with default config from environment.

        Returns:
            DuplicateAgent with configuration from environment variables
        """
        from ..config import Config

        config = Config.from_env()
        return cls(config.create_agent_config("duplicate_detector"))
