# Copyright (c) 2026 MergeGuide, Inc. All rights reserved.
# SPDX-License-Identifier: Proprietary
"""Azure DevOps Pull Request evaluation handler.

Orchestrates the full PR evaluation flow:
1. Post pending status
2. Fetch PR diff
3. Evaluate against policies
4. Store evidence (optional)
5. Post final status
6. Post/update comment thread

Example:
    auth = AzureDevOpsAuth(org_url, pat)
    api = AzureDevOpsAPI(auth)
    handler = AzureDevOpsPRHandler(api, policy_engine)
    result = await handler.handle_pull_request(event)
"""

from __future__ import annotations

import logging
import uuid
from dataclasses import dataclass
from typing import Any

from mergeguide.azure_devops.api import AzureDevOpsAPI
from mergeguide.azure_devops.comments import (
    COMMENT_MARKER,
    format_check_comment,
    format_error_comment,
)
from mergeguide.azure_devops.webhook import AzureDevOpsWebhookEvent
from mergeguide.engine.evaluator import PolicyEngine
from mergeguide.engine.parser import parse_diff
from mergeguide.engine.reporter import EvaluationReport

logger = logging.getLogger(__name__)


@dataclass
class PRCheckResult:
    """Result of a PR policy check.

    Attributes:
        passed: Whether the check passed (no blocking violations).
        evaluation_id: Unique identifier for this evaluation.
        violations_count: Total number of violations found.
        errors_count: Number of error-severity violations.
        warnings_count: Number of warning-severity violations.
        infos_count: Number of info-severity violations.
        thread_id: Comment thread ID (if comment was posted).
        evidence_key: S3 key for stored evidence (if stored).
    """

    passed: bool
    evaluation_id: str
    violations_count: int
    errors_count: int
    warnings_count: int
    infos_count: int = 0
    thread_id: int | None = None
    evidence_key: str | None = None


class AzureDevOpsPRHandler:
    """Handles Azure DevOps PR evaluation workflow.

    Orchestrates the complete flow from webhook event to
    posted status and comment.

    Attributes:
        api: Azure DevOps API client.
        engine: Policy evaluation engine.
        portal_url: Optional URL to MergeGuide portal for deep links.

    Example:
        >>> handler = AzureDevOpsPRHandler(api, engine)
        >>> result = await handler.handle_pull_request(event)
        >>> print(f"Check {'passed' if result.passed else 'failed'}")
    """

    def __init__(
        self,
        api: AzureDevOpsAPI,
        engine: PolicyEngine,
        portal_url: str | None = None,
    ) -> None:
        """Initialize the PR handler.

        Args:
            api: Configured Azure DevOps API client.
            engine: Policy evaluation engine with loaded rules.
            portal_url: Optional base URL for MergeGuide portal.
        """
        self.api = api
        self.engine = engine
        self.portal_url = portal_url

    async def handle_pull_request(
        self,
        event: AzureDevOpsWebhookEvent,
    ) -> PRCheckResult:
        """Handle a PR webhook event and run policy evaluation.

        This is the main entry point for PR evaluation. It:
        1. Validates the event should be processed
        2. Posts a pending status
        3. Fetches the PR diff
        4. Evaluates against policies
        5. Posts the final status and comment

        Args:
            event: Parsed webhook event from Azure DevOps.

        Returns:
            PRCheckResult with evaluation outcome.

        Raises:
            ValueError: If event should not be processed.
            Exception: Re-raised after posting error status/comment.
        """
        if not event.should_process_pr:
            raise ValueError(f"Event type {event.event_type} should not be processed")

        # Extract PR info
        pr_info = event.get_pr_info()
        org_url, project, repo_name = event.get_repository_info()

        pr_id = pr_info["pr_id"]
        repo_id = pr_info["repo_id"]
        head_sha = pr_info["head_sha"]

        evaluation_id = f"eval_{uuid.uuid4().hex[:12]}"

        logger.info(f"Processing PR #{pr_id} on {project}/{repo_name} (commit: {head_sha[:8]})")

        # Build target URL for status
        target_url = None
        if self.portal_url:
            target_url = f"{self.portal_url}/evaluations/{evaluation_id}"

        # Post pending status
        try:
            await self.api.post_status(
                project=project,
                repo_id=repo_id,
                pr_id=pr_id,
                commit_sha=head_sha,
                state="pending",
                description="MergeGuide is checking this PR...",
                target_url=target_url,
            )
        except Exception as e:
            logger.warning(f"Failed to post pending status: {e}")

        try:
            # Fetch PR diff
            diff = await self.api.fetch_pr_diff(project, repo_id, pr_id)

            if not diff:
                logger.info(f"No diff found for PR #{pr_id}")
                # Post success status for empty diff
                await self.api.post_status(
                    project=project,
                    repo_id=repo_id,
                    pr_id=pr_id,
                    commit_sha=head_sha,
                    state="success",
                    description="No files to check",
                    target_url=target_url,
                )
                return PRCheckResult(
                    passed=True,
                    evaluation_id=evaluation_id,
                    violations_count=0,
                    errors_count=0,
                    warnings_count=0,
                )

            # Parse diff into file changes
            file_changes = parse_diff(diff)

            # Evaluate against policies
            report = await self._evaluate_diff(
                file_changes,
                evaluation_id,
                pr_info,
            )

            # Determine pass/fail
            passed = report.summary.passed

            # Post final status
            status_state = "success" if passed else "failure"
            status_desc = self._format_status_description(report)

            await self.api.post_status(
                project=project,
                repo_id=repo_id,
                pr_id=pr_id,
                commit_sha=head_sha,
                state=status_state,
                description=status_desc,
                target_url=target_url,
            )

            # Post or update comment
            thread_id = await self._post_or_update_comment(
                project,
                repo_id,
                pr_id,
                report,
                evaluation_id,
                target_url,
            )

            return PRCheckResult(
                passed=passed,
                evaluation_id=evaluation_id,
                violations_count=report.summary.total_violations,
                errors_count=report.summary.errors,
                warnings_count=report.summary.warnings,
                infos_count=report.summary.infos,
                thread_id=thread_id,
            )

        except Exception as e:
            logger.error(f"PR evaluation failed: {e}")

            # Post error status
            try:
                await self.api.post_status(
                    project=project,
                    repo_id=repo_id,
                    pr_id=pr_id,
                    commit_sha=head_sha,
                    state="error",
                    description=f"Check failed: {str(e)[:100]}",
                    target_url=target_url,
                )
            except Exception as status_error:
                logger.warning(f"Failed to post error status: {status_error}")

            # Post error comment
            try:
                error_comment = format_error_comment(str(e))
                await self.api.create_thread(
                    project=project,
                    repo_id=repo_id,
                    pr_id=pr_id,
                    content=error_comment,
                )
            except Exception as comment_error:
                logger.warning(f"Failed to post error comment: {comment_error}")

            raise

    async def _evaluate_diff(
        self,
        file_changes: list[Any],
        evaluation_id: str,
        pr_info: dict[str, Any],
    ) -> EvaluationReport:
        """Evaluate file changes against policies.

        Args:
            file_changes: Parsed DiffFile objects from diff.
            evaluation_id: Unique evaluation identifier.
            pr_info: PR metadata for context.

        Returns:
            EvaluationReport with results.
        """
        import time

        from mergeguide.engine.parser import LineType

        start_time = time.perf_counter()

        violations = []
        files_checked = []

        for diff_file in file_changes:
            # Skip binary files
            if diff_file.is_binary:
                continue

            file_path = diff_file.path
            if not file_path:
                continue

            files_checked.append(file_path)

            # Get file content from added lines in all hunks
            content_lines = []
            for hunk in diff_file.hunks:
                for line in hunk.lines:
                    if line.line_type in (LineType.ADDITION, LineType.CONTEXT):
                        content_lines.append(line.content)

            content = "\n".join(content_lines)

            if not content.strip():
                continue

            # Evaluate content
            result = self.engine.evaluate_content(content, file_path)
            for v in result.violations:
                # Update file path in violation
                v.file_path = file_path
                violations.append(v)

        duration_ms = int((time.perf_counter() - start_time) * 1000)

        return EvaluationReport.create(
            evaluation_id=evaluation_id,
            violations=violations,
            files_checked=files_checked,
            duration_ms=duration_ms,
        )

    def _format_status_description(self, report: EvaluationReport) -> str:
        """Format status description from report."""
        if report.summary.passed:
            if report.summary.warnings > 0:
                return f"Passed with {report.summary.warnings} warning(s)"
            return "All checks passed"
        else:
            return f"Found {report.summary.errors} error(s)"

    async def _post_or_update_comment(
        self,
        project: str,
        repo_id: str,
        pr_id: int,
        report: EvaluationReport,
        evaluation_id: str,
        target_url: str | None,
    ) -> int | None:
        """Post a new comment or update existing one.

        Searches for existing MergeGuide comment and updates it
        if found, otherwise creates a new thread.

        Args:
            project: Project name.
            repo_id: Repository GUID.
            pr_id: Pull request ID.
            report: Evaluation report.
            evaluation_id: Evaluation ID for linking.
            target_url: Optional URL for details link.

        Returns:
            Thread ID of created/updated comment.
        """
        comment_content = format_check_comment(
            report,
            evaluation_id,
            target_url,
        )

        # Try to find existing comment
        existing = await self.api.find_bot_thread(project, repo_id, pr_id, COMMENT_MARKER)

        if existing:
            thread_id, comment_id = existing
            try:
                await self.api.update_thread_comment(
                    project=project,
                    repo_id=repo_id,
                    pr_id=pr_id,
                    thread_id=thread_id,
                    comment_id=comment_id,
                    content=comment_content,
                )
                return thread_id
            except Exception as e:
                logger.warning(f"Failed to update existing comment: {e}")
                # Fall through to create new comment

        # Create new thread
        return await self.api.create_thread(
            project=project,
            repo_id=repo_id,
            pr_id=pr_id,
            content=comment_content,
        )
