# Copyright (c) 2025 Airbyte, Inc., all rights reserved.
"""CLI commands for Devin session operations.

Commands:
    airbyte-ops devin secret-request - Request or deliver a secret on demand
"""

from __future__ import annotations

import logging
import os
import subprocess
from typing import Annotated

import requests
from cyclopts import Parameter
from uuid_extensions import uuid7str

from airbyte_ops_mcp.cli._base import App, app
from airbyte_ops_mcp.cli._shared import exit_with_error, print_json, print_success
from airbyte_ops_mcp.devin_api import (
    extract_session_id,
    resolve_session_url,
    send_session_message,
)
from airbyte_ops_mcp.human_in_the_loop import dispatch_escalation
from airbyte_ops_mcp.onepassword import DEFAULT_SHARE_EXPIRY, create_share_link
from airbyte_ops_mcp.slack_api import (
    SlackAPIError,
    SlackApprovalRecordError,
    SlackURLParseError,
    validate_slack_approval_record,
)

logger = logging.getLogger(__name__)

# ---------------------------------------------------------------------------
# Constants
# ---------------------------------------------------------------------------

SECRET_REQUEST_CHANNEL = "human-in-the-loop"

# ---------------------------------------------------------------------------
# Sub-app registration
# ---------------------------------------------------------------------------

devin_app = App(name="devin", help="Devin session operations.")
app.command(devin_app)


# ---------------------------------------------------------------------------
# CLI command
# ---------------------------------------------------------------------------


@devin_app.command(name="secret-request")
def secret_request(
    session: Annotated[
        str,
        Parameter(
            help="Devin session ID or URL "
            "(e.g. 'abc123...' or 'https://app.devin.ai/sessions/abc123...').",
        ),
    ],
    secret_alias: Annotated[
        str,
        Parameter(
            help="Secret name matching an item title in the "
            "'devin-on-demand-secrets' 1Password vault.",
        ),
    ],
    approval_evidence_url: Annotated[
        str | None,
        Parameter(
            help="Slack approval record URL. If provided, validates the "
            "approval and delivers the secret. If omitted, sends an "
            "approval request to Slack.",
        ),
    ] = None,
    target_approver: Annotated[
        str | None,
        Parameter(
            help="Person to notify for approval (GitHub handle, email, or "
            "Slack user ID). Required when requesting approval.",
        ),
    ] = None,
    expected_request_id: Annotated[
        str | None,
        Parameter(
            help="Request ID (UUIDv7) from Phase 1. When provided, the "
            "approval record is validated against this ID for replay "
            "protection.",
        ),
    ] = None,
) -> None:
    """Request or deliver a Devin secret.

    Without --approval-evidence-url: sends an approval request to Slack.
    With --approval-evidence-url: validates the approval, creates a
    1Password share link, and sends it to the Devin session.
    """
    try:
        session_id = extract_session_id(session)
    except ValueError as exc:
        exit_with_error(str(exc))
        return  # unreachable, but keeps the type checker happy

    if not approval_evidence_url:
        if not target_approver:
            exit_with_error(
                "--target-approver is required when requesting approval "
                "(no --approval-evidence-url provided)."
            )
            return  # unreachable
        _handle_approval_request(
            session_id=session_id,
            session_url=resolve_session_url(session),
            secret_alias=secret_alias,
            target_approver=target_approver,
        )
        return

    _handle_secret_delivery(
        session_id=session_id,
        secret_alias=secret_alias,
        approval_evidence_url=approval_evidence_url,
        expected_request_id=expected_request_id,
    )


# ---------------------------------------------------------------------------
# Phase handlers
# ---------------------------------------------------------------------------


def _handle_approval_request(
    session_id: str,
    session_url: str,
    secret_alias: str,
    target_approver: str,
) -> None:
    """Phase 1: Send an approval request to Slack via the HITL workflow."""
    approver = target_approver
    short_session = session_id[:8]
    request_id = uuid7str()

    result = dispatch_escalation(
        target_person=approver,
        message=(
            f"*Secret access requested:* `{secret_alias}`\n"
            f"*Requesting session:* `{short_session}...`\n\n"
            f"A Devin session is requesting access to the "
            f"`{secret_alias}` secret. If approved, a time-limited "
            f"1Password share link will be sent to the requesting "
            f"session.\n\n"
            f"Please review and approve or reject this request."
        ),
        agent_session_url=session_url,
        approval_requested=True,
        approval_request_summary=(
            f"Grant access to secret `{secret_alias}` for Devin session {short_session}"
        ),
        approval_metadata={
            "secret_alias": secret_alias,
            "request_id": request_id,
        },
        channel_override=SECRET_REQUEST_CHANNEL,
        header_emoji="🔑",
        header_label="Secret Access Request",
    )

    print_success(f"Approval request sent to #{SECRET_REQUEST_CHANNEL}")
    print_json(
        {
            "phase": "approval_requested",
            "secret_alias": secret_alias,
            "session_id": session_id,
            "request_id": request_id,
            "workflow_url": result.workflow_url,
            "run_id": result.run_id,
            "run_url": result.run_url,
        }
    )


def _handle_secret_delivery(
    session_id: str,
    secret_alias: str,
    approval_evidence_url: str,
    *,
    expected_request_id: str | None = None,
) -> None:
    """Phase 2: Validate approval, create share link, deliver to session."""
    # Step 1: Validate the Slack approval record
    logger.info("Validating approval evidence: %s", approval_evidence_url)
    try:
        record = validate_slack_approval_record(
            approval_evidence_url,
            expected_secret_alias=secret_alias,
            expected_request_id=expected_request_id,
        )
    except (SlackURLParseError, SlackAPIError, SlackApprovalRecordError) as exc:
        exit_with_error(str(exc))
        return  # unreachable

    approver = record.user_name or record.user_id
    logger.info("Approval validated: approved by %s", approver)

    # Step 2: Create 1Password share link
    logger.info("Creating 1Password share link for '%s'...", secret_alias)
    try:
        share_link = create_share_link(secret_alias)
    except (RuntimeError, subprocess.TimeoutExpired) as exc:
        exit_with_error(f"Failed to create share link: {exc}")
        return  # unreachable

    # Mask the share link in GitHub Actions logs
    if os.environ.get("GITHUB_ACTIONS"):
        print(f"::add-mask::{share_link}")

    logger.info("Share link created (expires in %s)", DEFAULT_SHARE_EXPIRY)

    # Step 3: Send to Devin session
    logger.info("Sending share link to session %s...", session_id[:8])
    message = (
        f"Your requested secret `{secret_alias}` is ready.\n\n"
        f"Open this link in your browser to view and copy the secret values:\n"
        f"{share_link}\n\n"
        f"This link expires in {DEFAULT_SHARE_EXPIRY}. "
        f"Retrieve and store the value immediately in your secure local storage."
    )
    try:
        send_session_message(session_id, message)
    except (requests.RequestException, RuntimeError) as exc:
        exit_with_error(f"Failed to send message to session: {exc}")
        return  # unreachable

    print_success("Secret delivered successfully!")
    print_json(
        {
            "phase": "delivered",
            "secret_alias": secret_alias,
            "session_id": session_id,
            "approver": approver,
        }
    )
