"""Microsoft Teams channel via Microsoft Graph API.

Uses the Bot Framework pattern: register a bot in Azure, receive webhooks
for incoming messages, send replies via Graph API.

Requirements:
- Azure Bot registration (Bot Framework)
- Microsoft Graph API credentials (client_id, client_secret, tenant_id)
- Bot endpoint URL for receiving webhooks
"""

from __future__ import annotations

import logging
from collections.abc import Callable
from typing import Any

import httpx

from agntspace.channels.base import Channel, ChannelMessage

log = logging.getLogger(__name__)

_AUTH_URL = "https://login.microsoftonline.com/{tenant_id}/oauth2/v2.0/token"
_GRAPH_URL = "https://graph.microsoft.com/v1.0"


class TeamsChannel(Channel):
    """Microsoft Teams channel via Bot Framework + Graph API."""

    name = "teams"

    def __init__(self, client_id: str, client_secret: str, tenant_id: str, bot_id: str = "") -> None:
        self._client_id = client_id
        self._client_secret = client_secret
        self._tenant_id = tenant_id
        self._bot_id = bot_id
        self._access_token: str = ""
        self._connected = False
        self._message_callback: Callable | None = None
        self._chat_id: str | None = None
        self._client: httpx.AsyncClient | None = None

    # ── auth ─────────────────────────────────────────────────────────

    async def _get_token(self) -> str:
        """Get an OAuth2 access token via client credentials flow."""
        if not self._client:
            return ""
        url = _AUTH_URL.format(tenant_id=self._tenant_id)
        data = {
            "client_id": self._client_id,
            "client_secret": self._client_secret,
            "scope": "https://graph.microsoft.com/.default",
            "grant_type": "client_credentials",
        }
        resp = await self._client.post(url, data=data)
        if resp.status_code != 200:
            raise ConnectionError(f"Teams auth failed ({resp.status_code}): {resp.text[:200]}")
        token_data = resp.json()
        self._access_token = token_data["access_token"]
        return self._access_token

    def _headers(self) -> dict[str, str]:
        return {"Authorization": f"Bearer {self._access_token}", "Content-Type": "application/json"}

    # ── lifecycle ────────────────────────────────────────────────────

    async def connect(self) -> None:
        """Authenticate with Microsoft Graph API."""
        self._client = httpx.AsyncClient(timeout=30.0)
        await self._get_token()

        # Verify token by listing joined teams
        resp = await self._client.get(f"{_GRAPH_URL}/me/joinedTeams", headers=self._headers())
        if resp.status_code == 200:
            teams = resp.json().get("value", [])
            log.info("Teams connected: %d teams accessible", len(teams))
        else:
            # For app-only tokens, /me won't work — that's OK
            log.info("Teams connected with app credentials (tenant: %s)", self._tenant_id)

        self._connected = True

    async def disconnect(self) -> None:
        self._connected = False
        self._access_token = ""
        if self._client:
            await self._client.aclose()
            self._client = None
        log.info("Teams disconnected")

    @property
    def is_connected(self) -> bool:
        return self._connected

    # ── sending ──────────────────────────────────────────────────────

    async def send_message(self, chat_id: str, text: str) -> None:
        """Send a message to a Teams chat or channel.

        chat_id format:
        - For 1:1 chats: the chat ID from Graph API
        - For channels: teamId/channelId
        """
        if not self._client or not self._access_token:
            return

        # Refresh token if needed
        if not self._access_token:
            await self._get_token()

        body = {
            "body": {
                "contentType": "text",
                "content": text,
            },
        }

        if "/" in chat_id:
            # Channel message: teamId/channelId
            team_id, channel_id = chat_id.split("/", 1)
            url = f"{_GRAPH_URL}/teams/{team_id}/channels/{channel_id}/messages"
        else:
            # 1:1 or group chat
            url = f"{_GRAPH_URL}/chats/{chat_id}/messages"

        resp = await self._client.post(url, headers=self._headers(), json=body)
        if resp.status_code not in (200, 201):
            log.warning("Teams send failed (%d): %s", resp.status_code, resp.text[:200])

    async def send_notification(self, chat_id: str, text: str) -> None:
        await self.send_message(chat_id, f"🔔 {text}")

    # ── webhook (Bot Framework activity) ─────────────────────────────

    async def handle_webhook(self, payload: dict[str, Any]) -> str | None:
        """Process an incoming Bot Framework activity.

        The Azure Bot sends activities to your configured endpoint.
        """
        activity_type = payload.get("type", "")
        if activity_type != "message":
            return None

        text = payload.get("text", "")
        if not text:
            return None

        sender_name = payload.get("from", {}).get("name", "User")
        sender_id = payload.get("from", {}).get("id", "")
        conversation = payload.get("conversation", {})
        chat_id = conversation.get("id", "")

        if not chat_id:
            return None

        self._chat_id = chat_id

        channel_msg = ChannelMessage(
            channel="teams",
            sender=sender_name,
            text=text,
            metadata={
                "chat_id": chat_id,
                "sender_id": sender_id,
                "conversation_type": conversation.get("conversationType", ""),
                "service_url": payload.get("serviceUrl", ""),
            },
        )

        if self._message_callback:
            response = await self._message_callback(channel_msg)
            if response:
                # For Bot Framework, reply via the serviceUrl
                service_url = payload.get("serviceUrl", "")
                if service_url:
                    await self._reply_to_activity(service_url, payload, response)
                else:
                    await self.send_message(chat_id, response)
            return response

        return None

    async def _reply_to_activity(self, service_url: str, activity: dict, text: str) -> None:
        """Reply to a Bot Framework activity via the serviceUrl."""
        if not self._client:
            return
        conversation_id = activity.get("conversation", {}).get("id", "")
        activity_id = activity.get("id", "")
        url = f"{service_url.rstrip('/')}/v3/conversations/{conversation_id}/activities/{activity_id}"

        reply = {
            "type": "message",
            "text": text,
            "from": {"id": self._bot_id or self._client_id},
            "conversation": activity.get("conversation", {}),
            "replyToId": activity_id,
        }

        resp = await self._client.post(url, headers=self._headers(), json=reply)
        if resp.status_code not in (200, 201, 202):
            log.warning("Teams reply failed (%d): %s", resp.status_code, resp.text[:200])
