# Copyright (c) 2025 Airbyte, Inc., all rights reserved.
"""Slack notification dispatch: workflow dispatch and person resolution.

This module provides the core logic for dispatching Slack notifications via
GitHub Actions. It is used by both the `escalate_to_human` and
`devin_session_feedback` MCP tools.

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_ci_trigger_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"


_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"


_AIRBYTE_EMAIL_DOMAIN = "@airbyte.io"


def validate_person_id(identifier: str) -> None:
    """Validate that a person identifier is well-formed.

    Accepted formats:
    - GitHub handle prefixed with `@` (e.g. `@aaronsteers`)
    - Slack user ID matching `U` + uppercase alphanumeric (e.g. `U05AKF1BCC9`)
    - Email address ending in `@airbyte.io` (e.g. `aj@airbyte.io`)

    A bare string without `@` prefix that does not match the Slack ID or
    email patterns is ambiguous and rejected.  The caller must use an
    explicit `@` prefix for GitHub handles.

    Args:
        identifier: Raw person identifier string.

    Raises:
        ValueError: If the identifier does not match any recognized format.
    """
    stripped = identifier.strip()
    if not stripped:
        raise ValueError("Person identifier is empty.")

    # @-prefixed → GitHub handle
    if stripped.startswith("@"):
        handle = stripped[1:]
        if not handle:
            raise ValueError(
                "Person identifier '@' is missing the GitHub handle after '@'."
            )
        return

    # Slack user ID
    if _SLACK_ID_PATTERN.match(stripped):
        return

    # Email
    if "@" in stripped and "." in stripped.split("@", 1)[-1]:
        if not stripped.endswith(_AIRBYTE_EMAIL_DOMAIN):
            raise ValueError(
                f"Person identifier '{stripped}' looks like an email but does not "
                f"end in '{_AIRBYTE_EMAIL_DOMAIN}'. Only Airbyte emails are accepted."
            )
        return

    raise ValueError(
        f"Person identifier '{stripped}' is not a recognized format. "
        "Use '@username' for a GitHub handle, an Airbyte email address "
        "(e.g. 'aj@airbyte.io'), or a Slack user ID (e.g. 'U05AKF1BCC9')."
    )


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,
    approval_requested: bool = False,
    approval_request_summary: str | None = None,
    approval_request_detail_url: str | None = None,
    approval_metadata: dict[str, str] | None = None,
    channel_override: str | None = None,
    header_emoji: str | None = None,
    header_label: str | None = None,
    context_footer: str | None = None,
    connector_name: str | None = None,
) -> WorkflowDispatchResult:
    """Dispatch a Slack notification via the 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 Slack.

    The `channel_override`, `header_emoji`, `header_label`, and
    `context_footer` parameters are set by the calling MCP tool and
    are NOT exposed to agents directly.

    Raises:
        ValueError: If `target_person` or any `cc` entry is not a
            recognized person identifier format.
    """
    validate_person_id(target_person)
    if cc:
        for person in cc:
            validate_person_id(person)

    token = resolve_ci_trigger_github_token()

    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)

    if approval_requested:
        workflow_inputs["approval_requested"] = "true"

    if approval_request_summary:
        workflow_inputs["approval_request_summary"] = approval_request_summary

    if approval_request_detail_url:
        workflow_inputs["approval_request_detail_url"] = approval_request_detail_url

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

    if channel_override:
        workflow_inputs["channel_override"] = channel_override

    if header_emoji:
        workflow_inputs["header_emoji"] = header_emoji

    if header_label:
        workflow_inputs["header_label"] = header_label

    if context_footer:
        workflow_inputs["context_footer"] = context_footer

    if connector_name:
        workflow_inputs["connector_name"] = connector_name

    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,
    )
