"""
AIPTX Slack Key Validator
=========================

Validates Slack Bot and User tokens.

Supports:
- Bot tokens (xoxb-)
- User tokens (xoxp-)
- App tokens (xoxa-)
"""

from __future__ import annotations

import re
import time
import logging
from typing import Any

import httpx

from ..base import (
    APIKeyInfo,
    ValidationResult,
    BaseKeyValidator,
    KeyType,
    RiskLevel,
)

logger = logging.getLogger(__name__)


class SlackKeyValidator(BaseKeyValidator):
    """
    Validate Slack tokens and enumerate permissions.

    Uses Slack Web API to validate tokens and discover:
    - Workspace/team information
    - User identity
    - Bot scopes and permissions
    - Accessible channels
    """

    API_BASE = "https://slack.com/api"

    def get_key_type(self) -> KeyType:
        return KeyType.SLACK

    def get_key_patterns(self) -> dict[str, re.Pattern]:
        return {
            "slack_bot": re.compile(r'xoxb-[0-9]+-[0-9]+-[A-Za-z0-9]+'),
            "slack_user": re.compile(r'xoxp-[0-9]+-[0-9]+-[0-9]+-[a-f0-9]+'),
            "slack_app": re.compile(r'xoxa-[0-9]+-[A-Za-z0-9]+'),
        }

    async def validate(self, key_info: APIKeyInfo) -> ValidationResult:
        """
        Validate Slack token against API.

        Calls auth.test to validate and retrieve identity.
        """
        start_time = time.time()

        try:
            async with httpx.AsyncClient(timeout=self.timeout) as client:
                # Validate token using auth.test
                response = await client.post(
                    f"{self.API_BASE}/auth.test",
                    headers={
                        "Authorization": f"Bearer {key_info.key_value}",
                        "Content-Type": "application/x-www-form-urlencoded",
                    },
                )

                duration_ms = (time.time() - start_time) * 1000
                data = response.json()

                if data.get("ok"):
                    # Token is valid
                    identity_info = {
                        "team": data.get("team"),
                        "team_id": data.get("team_id"),
                        "user": data.get("user"),
                        "user_id": data.get("user_id"),
                        "bot_id": data.get("bot_id"),
                        "enterprise_id": data.get("enterprise_id"),
                        "is_enterprise_install": data.get("is_enterprise_install", False),
                    }

                    # Determine token type
                    token_type = "bot" if key_info.key_value.startswith("xoxb") else "user"

                    # Get scopes and enumerate access
                    scopes = []
                    channels = []
                    users_count = 0

                    if token_type == "bot":
                        scopes, channels, users_count = await self._enumerate_bot_access(
                            client, key_info.key_value
                        )
                    else:
                        scopes, channels, users_count = await self._enumerate_user_access(
                            client, key_info.key_value
                        )

                    # Build permissions from scopes
                    permissions = self._scopes_to_permissions(scopes)

                    # Build resources list
                    resources = [f"channel:{c}" for c in channels[:20]]
                    if users_count:
                        resources.append(f"users:{users_count} workspace members")

                    result = ValidationResult(
                        key_info=key_info,
                        is_valid=True,
                        validation_method="auth.test",
                        validation_endpoint=f"{self.API_BASE}/auth.test",
                        identity_info=identity_info,
                        scopes=scopes,
                        permissions=permissions,
                        resources_accessible=resources,
                        raw_response=data,
                        validated_at=time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime()),
                        validation_duration_ms=duration_ms,
                    )

                    # Assess risk
                    result.risk_level = self._assess_slack_risk(scopes, channels, identity_info)
                    result.exploitation_potential = self._get_exploitation_potential(result)
                    result.attack_vectors = self._get_attack_vectors(scopes)

                    return result

                else:
                    error = data.get("error", "unknown_error")
                    return self._create_error_result(
                        key_info,
                        error=f"Slack API error: {error}",
                        error_code=error,
                        duration_ms=duration_ms,
                    )

        except httpx.TimeoutException:
            duration_ms = (time.time() - start_time) * 1000
            return self._create_error_result(
                key_info,
                error="Request timeout",
                error_code="TIMEOUT",
                duration_ms=duration_ms,
            )
        except Exception as e:
            duration_ms = (time.time() - start_time) * 1000
            logger.error(f"Slack validation error: {e}")
            return self._create_error_result(
                key_info,
                error=str(e),
                error_code="ERROR",
                duration_ms=duration_ms,
            )

    async def _enumerate_bot_access(
        self,
        client: httpx.AsyncClient,
        token: str,
    ) -> tuple[list[str], list[str], int]:
        """Enumerate bot token access."""
        headers = {"Authorization": f"Bearer {token}"}
        scopes = []
        channels = []
        users_count = 0

        try:
            # Get auth info for scopes
            response = await client.get(
                f"{self.API_BASE}/auth.test",
                headers=headers,
            )
            # Scopes are in response headers for some endpoints
            # or we can infer from what methods work

            # Try to list conversations
            response = await client.get(
                f"{self.API_BASE}/conversations.list",
                headers=headers,
                params={"types": "public_channel,private_channel", "limit": 100},
            )
            if response.status_code == 200:
                data = response.json()
                if data.get("ok"):
                    channels = [c["name"] for c in data.get("channels", [])]
                    scopes.append("channels:read")

            # Try to list users
            response = await client.get(
                f"{self.API_BASE}/users.list",
                headers=headers,
                params={"limit": 1},
            )
            if response.status_code == 200:
                data = response.json()
                if data.get("ok"):
                    # Get approximate count from response metadata
                    users_count = len(data.get("members", []))
                    scopes.append("users:read")
                    if any(m.get("profile", {}).get("email") for m in data.get("members", [])):
                        scopes.append("users:read.email")

            # Test file access
            response = await client.get(
                f"{self.API_BASE}/files.list",
                headers=headers,
                params={"count": 1},
            )
            if response.status_code == 200 and response.json().get("ok"):
                scopes.append("files:read")

        except Exception as e:
            logger.debug(f"Slack enumeration error: {e}")

        return scopes, channels, users_count

    async def _enumerate_user_access(
        self,
        client: httpx.AsyncClient,
        token: str,
    ) -> tuple[list[str], list[str], int]:
        """Enumerate user token access."""
        # User tokens typically have broader access
        # Start with same enumeration as bot
        return await self._enumerate_bot_access(client, token)

    def _scopes_to_permissions(self, scopes: list[str]) -> list[str]:
        """Convert Slack scopes to permission descriptions."""
        scope_descriptions = {
            "channels:read": "View basic channel information",
            "channels:write": "Create and archive channels",
            "channels:history": "View messages in public channels",
            "chat:write": "Send messages as the bot/user",
            "chat:write.public": "Send messages to public channels",
            "files:read": "View files shared in channels",
            "files:write": "Upload, edit, and delete files",
            "groups:read": "View private channels",
            "groups:write": "Create private channels",
            "groups:history": "View messages in private channels",
            "im:read": "View direct messages",
            "im:write": "Start direct messages",
            "im:history": "View direct message history",
            "mpim:read": "View group direct messages",
            "mpim:write": "Start group direct messages",
            "users:read": "View people in the workspace",
            "users:read.email": "View email addresses",
            "users:write": "Modify user profile",
            "team:read": "View team information",
            "admin": "Full admin access",
            "admin.conversations:write": "Manage all channels",
            "admin.users:write": "Manage workspace members",
        }

        permissions = []
        for scope in scopes:
            desc = scope_descriptions.get(scope, f"{scope} permission")
            permissions.append(desc)

        return permissions

    def _assess_slack_risk(
        self,
        scopes: list[str],
        channels: list[str],
        identity: dict,
    ) -> RiskLevel:
        """Assess risk level based on token capabilities."""
        # Critical: admin access or enterprise install
        if any("admin" in s for s in scopes) or identity.get("is_enterprise_install"):
            return RiskLevel.CRITICAL

        # High: write access to messages or files
        write_scopes = {"chat:write", "files:write", "channels:write", "groups:write"}
        if any(s in scopes for s in write_scopes):
            return RiskLevel.HIGH

        # Medium: can read messages or access private channels
        read_scopes = {"channels:history", "groups:history", "im:history", "groups:read"}
        if any(s in scopes for s in read_scopes):
            return RiskLevel.MEDIUM

        # Low: basic read access
        if channels or scopes:
            return RiskLevel.LOW

        return RiskLevel.INFO

    def _get_exploitation_potential(self, result: ValidationResult) -> str:
        """Get exploitation potential description."""
        if result.risk_level == RiskLevel.CRITICAL:
            return "Critical - Full admin access to workspace"
        elif result.risk_level == RiskLevel.HIGH:
            return "High - Can send messages, access files, and modify channels"
        elif result.risk_level == RiskLevel.MEDIUM:
            return "Medium - Can read message history and private channels"
        elif result.risk_level == RiskLevel.LOW:
            return "Low - Limited read access to channels"
        return "Minimal - Token has very limited permissions"

    def _get_attack_vectors(self, scopes: list[str]) -> list[str]:
        """Identify potential attack vectors."""
        vectors = []

        if any("write" in s for s in scopes):
            vectors.append("Send phishing messages to workspace members")

        if any("history" in s for s in scopes):
            vectors.append("Exfiltrate sensitive conversations and data")

        if "files:read" in scopes or "files:write" in scopes:
            vectors.append("Access or upload malicious files")

        if "users:read.email" in scopes:
            vectors.append("Harvest email addresses for targeted attacks")

        if any("admin" in s for s in scopes):
            vectors.append("Modify workspace settings and permissions")
            vectors.append("Add malicious integrations")

        if any("im" in s for s in scopes):
            vectors.append("Access private direct messages")

        return vectors
