# Copyright (c) 2026 MergeGuide, Inc. All rights reserved.
# SPDX-License-Identifier: Proprietary
"""Bitbucket PR handler for policy evaluation.

Orchestrates the policy evaluation workflow for Bitbucket PRs:
1. Receive webhook event
2. Post pending status
3. Fetch PR diff
4. Evaluate against policies
5. Post results (status + comment)
"""

from __future__ import annotations

import logging
from dataclasses import dataclass
from typing import TYPE_CHECKING

from .api import BitbucketAPI, BuildState
from .comments import format_error_comment, format_pending_comment, format_pr_comment

if TYPE_CHECKING:
    from mergeguide.engine.evaluator import PolicyEngine
    from mergeguide.engine.orchestrator import DualLayerOrchestrator
    from mergeguide.engine.reporter import EvaluationReport
    from mergeguide.evidence.storage import EvidenceStorage

    from .webhook import WebhookEvent

logger = logging.getLogger(__name__)


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

    Attributes:
        passed: Whether all checks passed
        report: Full evaluation report
        workspace: Bitbucket workspace
        repo_slug: Repository slug
        pr_id: Pull request ID
        commit: Commit hash that was checked
    """

    passed: bool
    report: EvaluationReport
    workspace: str
    repo_slug: str
    pr_id: int
    commit: str


class PRHandler:
    """Handles PR webhook events and policy evaluation.

    Coordinates the full policy check workflow for Bitbucket PRs:
    1. Parse webhook event
    2. Post "in progress" status
    3. Fetch PR diff
    4. Run policy evaluation
    5. Post results (status + optional comment)

    Example:
        >>> from mergeguide.bitbucket import BitbucketAuth, BitbucketAPI, PRHandler
        >>>
        >>> auth = BitbucketAuth(username="user", app_password="xxx")
        >>> api = BitbucketAPI(auth)
        >>> handler = PRHandler(api)
        >>>
        >>> result = await handler.handle_pr_event(event)
        >>> if result.passed:
        ...     print("All checks passed!")
    """

    # Build status key for MergeGuide
    STATUS_KEY = "mergeguide-policy-check"
    STATUS_NAME = "MergeGuide Policy Check"

    def __init__(
        self,
        api: BitbucketAPI,
        policy_engine: PolicyEngine | DualLayerOrchestrator | None = None,
        evidence_storage: EvidenceStorage | None = None,
        signing_key: str = "",
        evaluations_table_name: str = "",
        organizations_table_name: str = "",
        aws_region: str = "us-east-1",
        post_comments: bool = True,
        dashboard_url_template: str | None = None,
    ) -> None:
        """Initialize PR handler.

        Args:
            api: BitbucketAPI instance for API calls
            policy_engine: MergeGuide policy engine for diff evaluation.
                Accepts either a PolicyEngine (regex-only) or a
                DualLayerOrchestrator (regex + Semgrep). When an
                orchestrator is provided, scan_diff() is used for
                dual-layer analysis. If None, falls back to creating
                a local PolicyEngine with default rules.
            evidence_storage: Optional storage backend for evidence artifacts.
                If None, evidence artifacts will not be persisted.
            signing_key: Optional HMAC key for signing evidence artifacts.
            evaluations_table_name: DynamoDB table name for evaluation records.
            organizations_table_name: DynamoDB table name for org lookup.
            aws_region: AWS region for DynamoDB client.
            post_comments: Whether to post PR comments (default: True)
            dashboard_url_template: URL template for dashboard links.
                Use {workspace}, {repo}, {pr_id} placeholders.
        """
        self.api = api
        self.engine = policy_engine
        self.storage = evidence_storage
        self.signing_key = signing_key
        self._evaluations_table_name = evaluations_table_name
        self._organizations_table_name = organizations_table_name
        self._aws_region = aws_region
        self.post_comments = post_comments
        self.dashboard_url_template = dashboard_url_template

    async def handle_pr_event(
        self,
        event: WebhookEvent,
        policies: list | None = None,
    ) -> PRCheckResult | None:
        """Handle a PR webhook event.

        Processes the event if it requires policy evaluation:
        - pullrequest:created
        - pullrequest:updated

        Args:
            event: Parsed webhook event
            policies: Optional list of policy rules to use

        Returns:
            PRCheckResult if evaluation was performed, None if skipped
        """
        if not event.should_process_pr:
            logger.debug(f"Skipping event {event.event_key} - not a PR evaluation trigger")
            return None

        # Extract PR info
        repo_info = event.get_repository_info()
        pr_info = event.get_pr_info()

        if not repo_info or not pr_info:
            logger.error("Could not extract repo/PR info from webhook")
            return None

        workspace, repo_slug = repo_info
        pr_id = pr_info["pr_id"]
        commit = pr_info["source_commit"]

        logger.info(f"Processing PR {workspace}/{repo_slug}!{pr_id} @ {commit[:7]}")

        try:
            # Post pending status
            await self._post_pending_status(workspace, repo_slug, commit)

            # Post pending comment if enabled
            if self.post_comments:
                await self._post_or_update_comment(
                    workspace, repo_slug, pr_id, format_pending_comment()
                )

            # Fetch PR diff
            diff = await self.api.get_pr_diff(workspace, repo_slug, pr_id)

            # Run policy evaluation
            report = await self._evaluate_diff(diff, policies)

            # Post results
            await self._post_results(workspace, repo_slug, pr_id, commit, report)

            return PRCheckResult(
                passed=report.summary.passed,
                report=report,
                workspace=workspace,
                repo_slug=repo_slug,
                pr_id=pr_id,
                commit=commit,
            )

        except Exception as e:
            logger.exception(f"Error processing PR {workspace}/{repo_slug}!{pr_id}")

            # Post error status
            await self._post_error_status(workspace, repo_slug, commit, str(e))

            # Post error comment if enabled
            if self.post_comments:
                await self._post_or_update_comment(
                    workspace, repo_slug, pr_id, format_error_comment(str(e))
                )

            raise

    async def _post_pending_status(self, workspace: str, repo_slug: str, commit: str) -> None:
        """Post in-progress build status."""
        await self.api.post_build_status(
            workspace=workspace,
            repo_slug=repo_slug,
            commit=commit,
            state="INPROGRESS",
            key=self.STATUS_KEY,
            name=self.STATUS_NAME,
            description="Evaluating policy compliance...",
        )

    async def _post_error_status(
        self, workspace: str, repo_slug: str, commit: str, error: str
    ) -> None:
        """Post error build status."""
        await self.api.post_build_status(
            workspace=workspace,
            repo_slug=repo_slug,
            commit=commit,
            state="FAILED",
            key=self.STATUS_KEY,
            name=self.STATUS_NAME,
            description=f"Error: {error[:200]}",
        )

    async def _post_results(
        self,
        workspace: str,
        repo_slug: str,
        pr_id: int,
        commit: str,
        report: EvaluationReport,
    ) -> None:
        """Post evaluation results (status + comment)."""
        # Determine status
        if report.summary.passed:
            state: BuildState = "SUCCESSFUL"
            description = "All policy checks passed"
        else:
            state = "FAILED"
            errors = report.summary.error_count
            warnings = report.summary.warning_count
            description = f"Found {errors} error(s), {warnings} warning(s)"

        # Build dashboard URL if template provided
        dashboard_url = None
        if self.dashboard_url_template:
            dashboard_url = self.dashboard_url_template.format(
                workspace=workspace,
                repo=repo_slug,
                pr_id=pr_id,
            )

        # Post build status
        await self.api.post_build_status(
            workspace=workspace,
            repo_slug=repo_slug,
            commit=commit,
            state=state,
            key=self.STATUS_KEY,
            name=self.STATUS_NAME,
            description=description,
            url=dashboard_url,
        )

        # Post comment if enabled
        if self.post_comments:
            comment = format_pr_comment(report, dashboard_url)
            await self._post_or_update_comment(workspace, repo_slug, pr_id, comment)

    async def _post_or_update_comment(
        self,
        workspace: str,
        repo_slug: str,
        pr_id: int,
        content: str,
    ) -> None:
        """Post new comment or update existing MergeGuide comment."""
        # Look for existing comment to update
        existing_id = await self.api.find_bot_comment(workspace, repo_slug, pr_id)

        if existing_id:
            await self.api.update_pr_comment(workspace, repo_slug, pr_id, existing_id, content)
        else:
            await self.api.post_pr_comment(workspace, repo_slug, pr_id, content)

    async def _evaluate_diff(self, diff: str, policies: list | None = None) -> EvaluationReport:
        """Evaluate a diff against policies.

        Uses the injected policy engine (DualLayerOrchestrator or PolicyEngine)
        when available. Falls back to creating a local PolicyEngine with default
        rules for backward compatibility.

        Args:
            diff: Unified diff string
            policies: Optional policy rules (uses defaults if None)

        Returns:
            EvaluationReport with results
        """
        from mergeguide.engine.evaluator import EvaluationContext, PolicyEngine
        from mergeguide.engine.orchestrator import DualLayerOrchestrator

        # If an engine was injected, use dual-layer or standard evaluation
        if self.engine is not None:
            context = EvaluationContext(
                org_id="",
                repo="",
                branch="",
                commit_sha="",
                author="",
            )

            if isinstance(self.engine, DualLayerOrchestrator):
                output = self.engine.scan_diff(diff, context)
            else:
                output = self.engine.evaluate_diff(diff, context)

            # Store evidence if storage is configured
            await self._store_evidence(output, context, len(diff))

            return output.report

        # Fallback: create a local PolicyEngine with default rules
        from mergeguide.engine.parser import parse_diff
        from mergeguide.policies.defaults import get_default_rules

        files = parse_diff(diff)
        rules = policies if policies is not None else get_default_rules()
        engine = PolicyEngine(rules=rules)
        result = engine.evaluate_files(files)

        return result.report

    async def _store_evidence(
        self,
        output: object,
        context: object,
        diff_size: int,
    ) -> str | None:
        """Store an evidence artifact for the evaluation in configured storage.

        Args:
            output: PolicyOutput from evaluation
            context: EvaluationContext with metadata
            diff_size: Size of the diff in bytes

        Returns:
            S3 key for the stored artifact, or None if storage not configured
        """
        if not self.storage:
            return None

        try:
            from mergeguide.engine.orchestrator import DualLayerOrchestrator
            from mergeguide.evidence.artifact import EvidenceArtifact, SourceInfo

            source = SourceInfo(
                type="diff",
                location="bitbucket-pr",
                ref="",
                size_bytes=diff_size,
            )

            # Resolve rule list from either PolicyEngine or DualLayerOrchestrator
            if isinstance(self.engine, DualLayerOrchestrator):
                _rule_engine = self.engine.regex_engine.rule_engine
            elif self.engine is not None:
                _rule_engine = self.engine.rule_engine
            else:
                return None

            artifact = EvidenceArtifact.from_output(
                output=output,
                source=source,
                context=context,
                input_content="",
                policies_used=[r.id for r in _rule_engine.rules],
            )

            if self.signing_key:
                artifact.sign(self.signing_key)

            evidence_key = self.storage.store(
                artifact=artifact,
                org_id=getattr(context, "org_id", "unknown"),
                repo=getattr(context, "repo", "unknown"),
            )

            logger.info(f"Stored evidence at: {evidence_key}")
            return evidence_key

        except Exception as e:
            logger.warning(f"Failed to store evidence: {e}")
            return None
