# Copyright (c) 2025 Airbyte, Inc., all rights reserved.
"""Human-in-the-loop escalation: workflow dispatch and person resolution.

This module provides the core logic for the human-in-the-loop escalation tool.
It dispatches a GitHub Actions workflow that posts a Slack message to the
#human-in-the-loop channel, tagging the specified team members.

The Slack API call happens inside CI (not at MCP runtime) so that Slack tokens
are never exposed to the calling agent. The workflow resolves person identifiers
to Slack user IDs using the internal team roster artifact.

Person identifier conventions:
- ``@username`` → GitHub handle (the leading ``@`` is stripped)
- ``U`` followed by uppercase alphanumeric (e.g. ``U05AKF1BCC9``) → Slack user ID
- Contains ``@`` with a domain part (e.g. ``aj@airbyte.io``) → email address
"""

from __future__ import annotations

import json
import logging
import re

from airbyte_ops_mcp.github_actions import (
    WorkflowDispatchResult,
    trigger_workflow_dispatch,
)
from airbyte_ops_mcp.github_api import resolve_github_token

logger = logging.getLogger(__name__)

HITL_REPO_OWNER = "airbytehq"
HITL_REPO_NAME = "airbyte-ops-mcp"
HITL_WORKFLOW_FILE = "human-in-the-loop.yml"
HITL_DEFAULT_BRANCH = "main"

WORKFLOW_TRIGGER_TOKEN_ENV_VARS = [
    "GITHUB_CI_WORKFLOW_TRIGGER_PAT",
    "GITHUB_TOKEN",
]

_SLACK_ID_PATTERN = re.compile(r"^U[A-Z0-9]{8,}$")


def classify_person_id(identifier: str) -> str:
    """Classify a person identifier string.

    Args:
        identifier: A person identifier (email, GitHub handle, or Slack ID).

    Returns:
        One of ``"github_handle"``, ``"slack_id"``, or ``"email"``.
    """
    identifier = identifier.strip()
    if identifier.startswith("@"):
        return "github_handle"
    if _SLACK_ID_PATTERN.match(identifier):
        return "slack_id"
    if "@" in identifier and "." in identifier.split("@", 1)[-1]:
        return "email"
    return "github_handle"


def normalize_person_id(identifier: str) -> str:
    """Normalize a person identifier for workflow input.

    Strips leading ``@`` from GitHub handles and trims whitespace.

    Args:
        identifier: Raw person identifier string.

    Returns:
        Normalized identifier string.
    """
    identifier = identifier.strip()
    if identifier.startswith("@"):
        return identifier[1:]
    return identifier


def dispatch_escalation(
    target_person: str,
    message: str,
    agent_session_url: str,
    cc: list[str] | None = None,
    pr_url: str | None = None,
    issue_url: str | None = None,
    additional_actions: dict[str, str] | None = None,
) -> WorkflowDispatchResult:
    """Dispatch a human-in-the-loop escalation via GitHub Actions workflow.

    Triggers the ``human-in-the-loop.yml`` workflow which resolves person
    identifiers to Slack user IDs (using the roster artifact) and posts
    a formatted message to ``#human-in-the-loop``.

    Args:
        target_person: Primary person to address (email, ``@github_handle``,
            or Slack user ID).
        message: The message body to send.
        agent_session_url: Agent session URL for context (e.g. Devin session URL).
        cc: Optional list of additional people to tag.
        pr_url: Optional URL to a related pull request.
        issue_url: Optional URL to a related GitHub issue.
        additional_actions: Optional dict of label → URL for extra action buttons.

    Returns:
        WorkflowDispatchResult with workflow URL and optionally run ID/URL.

    Raises:
        ValueError: If no GitHub token is found.
        requests.HTTPError: If the workflow dispatch API call fails.
    """
    token = resolve_github_token(preferred_env_vars=WORKFLOW_TRIGGER_TOKEN_ENV_VARS)

    workflow_inputs: dict[str, str] = {
        "target_person": normalize_person_id(target_person),
        "message": message,
        "agent_session_url": agent_session_url,
    }

    if cc:
        normalized_cc = [normalize_person_id(person) for person in cc]
        workflow_inputs["cc_persons"] = ",".join(normalized_cc)

    if pr_url:
        workflow_inputs["pr_url"] = pr_url

    if issue_url:
        workflow_inputs["issue_url"] = issue_url

    if additional_actions:
        workflow_inputs["additional_actions"] = json.dumps(additional_actions)

    return trigger_workflow_dispatch(
        owner=HITL_REPO_OWNER,
        repo=HITL_REPO_NAME,
        workflow_file=HITL_WORKFLOW_FILE,
        ref=HITL_DEFAULT_BRANCH,
        inputs=workflow_inputs,
        token=token,
    )
