"""Slack channel implementation.

This module provides the Slack channel implementation for
receiving and sending messages via Slack.
"""

import hashlib
import hmac
import logging
import os
import time
from typing import Any, Dict, List, Optional

from agent_factory.channels.base import (
    BaseChannel,
    ChannelAccessDeniedError,
    ChannelError,
    ChannelMessage,
    ChannelResponse,
    ChannelSendError,
    ChannelType,
)

logger = logging.getLogger(__name__)


class SlackChannel(BaseChannel):
    """Slack channel implementation.

    Handles Slack webhook events and sends messages via the Slack API.

    Example:
        channel = SlackChannel(
            bot_token="xoxb-...",
            signing_secret="...",
        )

        # Parse incoming event
        message = channel.parse_event(slack_event)

        # Send response
        channel.send_message(ChannelResponse(
            text="Hello!",
            channel_id=message.channel_id,
        ))
    """

    def __init__(
        self,
        bot_token: Optional[str] = None,
        signing_secret: Optional[str] = None,
        allowed_channels: Optional[List[str]] = None,
    ):
        """Initialize Slack channel.

        Args:
            bot_token: Slack bot token (defaults to SLACK_BOT_TOKEN env var)
            signing_secret: Slack signing secret (defaults to SLACK_SIGNING_SECRET env var)
            allowed_channels: List of allowed channel IDs. If set, only messages
                from these channels will be processed. Defaults to
                SLACK_ALLOWED_CHANNELS env var (comma-separated list).
                If None/empty, all channels are allowed.
        """
        self._bot_token = bot_token or os.getenv("SLACK_BOT_TOKEN")
        self._signing_secret = signing_secret or os.getenv("SLACK_SIGNING_SECRET")

        # Parse allowed channels
        if allowed_channels is not None:
            self._allowed_channels = set(allowed_channels)
        else:
            env_channels = os.getenv("SLACK_ALLOWED_CHANNELS", "")
            if env_channels.strip():
                self._allowed_channels = {
                    ch.strip() for ch in env_channels.split(",") if ch.strip()
                }
            else:
                self._allowed_channels = None  # None means all channels allowed

    def is_channel_allowed(self, channel_id: str) -> bool:
        """Check if a channel is in the allowed list.

        Args:
            channel_id: Slack channel ID to check

        Returns:
            True if channel is allowed (or if no allowlist is configured)
        """
        if self._allowed_channels is None:
            return True
        return channel_id in self._allowed_channels

    @property
    def channel_type(self) -> ChannelType:
        return ChannelType.SLACK

    def verify_request(self, headers: Dict[str, str], body: bytes) -> bool:
        """Verify Slack request signature."""
        if not self._signing_secret:
            logger.warning("Slack signing secret not configured, skipping verification")
            return True

        timestamp = headers.get("X-Slack-Request-Timestamp", "")
        signature = headers.get("X-Slack-Signature", "")

        # Check timestamp is recent (within 5 minutes)
        try:
            if abs(time.time() - int(timestamp)) > 300:
                logger.warning("Slack request timestamp too old")
                return False
        except (ValueError, TypeError):
            return False

        # Compute expected signature
        sig_basestring = f"v0:{timestamp}:{body.decode('utf-8')}"
        expected = (
            "v0="
            + hmac.new(
                self._signing_secret.encode(),
                sig_basestring.encode(),
                hashlib.sha256,
            ).hexdigest()
        )

        return hmac.compare_digest(expected, signature)

    def is_url_verification(self, event: Dict[str, Any]) -> bool:
        """Check if the event is a URL verification challenge.

        Args:
            event: Slack event data

        Returns:
            True if this is a URL verification challenge
        """
        return event.get("type") == "url_verification"

    def get_url_verification_response(self, event: Dict[str, Any]) -> Dict[str, Any]:
        """Get the response for URL verification challenge.

        This returns the proper response format for Slack URL verification.
        The challenge must be returned as PLAIN TEXT, not JSON.

        Args:
            event: Slack event data containing the challenge

        Returns:
            Response dict with statusCode, headers, and body (plain text challenge)

        Example:
            if channel.is_url_verification(event):
                return channel.get_url_verification_response(event)
        """
        challenge = event.get("challenge", "")
        logger.info(
            "Handling URL verification challenge",
            extra={"challenge": challenge[:20] + "..." if len(challenge) > 20 else challenge},
        )
        return {
            "statusCode": 200,
            "headers": {"Content-Type": "text/plain"},
            "body": challenge,
        }

    def parse_event(self, event: Dict[str, Any]) -> Optional[ChannelMessage]:
        """Parse Slack event into ChannelMessage.

        Note: For URL verification, use is_url_verification() and
        get_url_verification_response() instead.
        """
        # Handle different event types
        event_type = event.get("type")

        if event_type == "url_verification":
            # URL verification challenge - caller should use get_url_verification_response()
            return None

        if event_type == "event_callback":
            inner_event = event.get("event", {})
            return self._parse_message_event(inner_event, event)

        return None

    def _parse_message_event(
        self, event: Dict[str, Any], envelope: Dict[str, Any]
    ) -> Optional[ChannelMessage]:
        """Parse a message event."""
        event_type = event.get("type")

        # Skip non-message events
        if event_type not in ("message", "app_mention"):
            return None

        # Skip bot messages to avoid loops
        if event.get("bot_id") or event.get("subtype") == "bot_message":
            return None

        text = event.get("text", "")
        channel_id = event.get("channel", "")
        user_id = event.get("user", "")
        thread_ts = event.get("thread_ts") or event.get("ts")

        if not text or not channel_id:
            return None

        # Check if channel is allowed
        if not self.is_channel_allowed(channel_id):
            logger.warning(
                "Message from unauthorized channel",
                extra={"channel_id": channel_id, "user_id": user_id},
            )
            raise ChannelAccessDeniedError(
                f"Channel {channel_id} is not in the allowed channels list",
                channel_id=channel_id,
                channel_type=ChannelType.SLACK,
            )

        return ChannelMessage(
            channel_type=ChannelType.SLACK,
            channel_id=channel_id,
            user_id=user_id,
            text=text,
            thread_id=thread_ts,
            metadata={
                "team_id": envelope.get("team_id"),
                "event_ts": event.get("event_ts"),
                "ts": event.get("ts"),
            },
            raw_event=envelope,
        )

    def send_message(self, response: ChannelResponse) -> bool:
        """Send message to Slack."""
        if not self._bot_token:
            raise ChannelSendError(
                "Slack bot token not configured",
                channel_type=ChannelType.SLACK,
            )

        try:
            import requests
        except ImportError:
            raise ChannelError(
                "requests is required for Slack. "
                "Install with: pip install agent-factory-core[slack]",
                channel_type=ChannelType.SLACK,
            )

        url = "https://slack.com/api/chat.postMessage"
        headers = {
            "Authorization": f"Bearer {self._bot_token}",
            "Content-Type": "application/json",
        }

        payload: Dict[str, Any] = {
            "channel": response.channel_id,
            "text": response.text,
            "mrkdwn": True,
        }

        if response.thread_id:
            payload["thread_ts"] = response.thread_id

        if response.blocks:
            payload["blocks"] = response.blocks

        try:
            resp = requests.post(url, headers=headers, json=payload, timeout=10)
            result = resp.json()

            if not result.get("ok"):
                logger.error(
                    "Slack API error",
                    extra={"error": result.get("error")},
                )
                return False

            return True

        except Exception as e:
            logger.exception("Failed to send Slack message")
            raise ChannelSendError(
                f"Failed to send Slack message: {e}",
                channel_type=ChannelType.SLACK,
                is_retryable=True,
            )

    def format_response(
        self,
        text: str,
        channel_id: str,
        blocks: Optional[List[Dict[str, Any]]] = None,
        **kwargs,
    ) -> ChannelResponse:
        """Format response for Slack."""
        return ChannelResponse(
            text=text,
            channel_id=channel_id,
            thread_id=kwargs.get("thread_id"),
            blocks=blocks,
            metadata=kwargs,
        )

    def get_context(self, message: ChannelMessage) -> Dict[str, Any]:
        """Get Slack-specific context."""
        return {
            "channel_type": self.channel_type.value,
            "channel_id": message.channel_id,
            "user_id": message.user_id,
            "slack_user_id": message.user_id,  # For tools that need this
            "team_id": message.metadata.get("team_id"),
            "thread_ts": message.thread_id,
        }

    def update_message(
        self,
        channel_id: str,
        ts: str,
        text: str,
        blocks: Optional[List[Dict[str, Any]]] = None,
    ) -> bool:
        """Update an existing Slack message."""
        if not self._bot_token:
            raise ChannelSendError(
                "Slack bot token not configured",
                channel_type=ChannelType.SLACK,
            )

        try:
            import requests
        except ImportError:
            raise ChannelError(
                "requests is required for Slack",
                channel_type=ChannelType.SLACK,
            )

        url = "https://slack.com/api/chat.update"
        headers = {
            "Authorization": f"Bearer {self._bot_token}",
            "Content-Type": "application/json",
        }

        payload: Dict[str, Any] = {
            "channel": channel_id,
            "ts": ts,
            "text": text,
        }

        if blocks:
            payload["blocks"] = blocks

        try:
            resp = requests.post(url, headers=headers, json=payload, timeout=10)
            result = resp.json()
            return result.get("ok", False)
        except Exception:
            return False

    def add_reaction(self, channel_id: str, ts: str, emoji: str) -> bool:
        """Add a reaction to a message."""
        if not self._bot_token:
            return False

        try:
            import requests
        except ImportError:
            return False

        url = "https://slack.com/api/reactions.add"
        headers = {
            "Authorization": f"Bearer {self._bot_token}",
            "Content-Type": "application/json",
        }

        payload = {
            "channel": channel_id,
            "timestamp": ts,
            "name": emoji,
        }

        try:
            resp = requests.post(url, headers=headers, json=payload, timeout=10)
            return resp.json().get("ok", False)
        except Exception:
            return False
