"""Discord / Slack webhook formatters for kanban lifecycle notifications.

Each formatter builds a platform-native payload and POSTs it to the
webhook URL. URL validation is strict: Discord must match the known
webhook pattern; Slack must be an Incoming Webhook URL. Both require
HTTPS. Timeouts and response-body caps mirror ``cron_scheduler._deliver_webhook``.
"""
from __future__ import annotations

import json
import logging
import re
import urllib.error
import urllib.request
from datetime import datetime, timezone

_log = logging.getLogger(__name__)

_TIMEOUT = 10
_MAX_RESPONSE = 8192

_DISCORD_URL_RE = re.compile(
    r"^https://(?:ptb\.|canary\.)?discord(?:app)?\.com/api/webhooks/\d+/.+$"
)
_SLACK_URL_RE = re.compile(
    r"^https://hooks\.slack\.com/services/.+$"
)


def _validate_url(kind: str, url: str) -> bool:
    if kind == "discord":
        return bool(_DISCORD_URL_RE.match(url))
    if kind == "slack":
        return bool(_SLACK_URL_RE.match(url))
    return False


def _status_emoji(event: str) -> str:
    return {
        "done": "\u2705",
        "gave_up": "\u274c",
        "cancelled": "\u23f9\ufe0f",
    }.get(event, "\U0001f514")


def _format_discord(banner: str, body: str, task_id: str, event: str) -> dict:
    emoji = _status_emoji(event)
    embed = {
        "title": f"{emoji} {banner[:256]}",
        "description": body[:4096],
        "color": 0x57F287 if event == "done" else 0xED4245,
        "footer": {"text": f"Task {task_id}"},
        "timestamp": datetime.now(tz=timezone.utc).isoformat(),
    }
    return {"embeds": [embed]}


def _format_slack(banner: str, body: str, task_id: str, event: str) -> dict:
    emoji = _status_emoji(event)
    blocks = [
        {
            "type": "header",
            "text": {"type": "plain_text", "text": f"{emoji} {banner[:150]}", "emoji": True},
        },
        {
            "type": "section",
            "text": {"type": "mrkdwn", "text": body[:3000] if body else "_No details._"},
        },
        {
            "type": "context",
            "elements": [{"type": "mrkdwn", "text": f"Task `{task_id}` | Event: `{event}`"}],
        },
    ]
    return {"blocks": blocks, "text": f"{emoji} {banner[:150]}"}


def deliver_chat_webhook(
    *,
    channel_kind: str,
    url: str,
    banner: str,
    body: str,
    task_id: str = "",
    event: str = "",
) -> None:
    """Build the platform-specific payload and POST to *url*.

    Raises on transport errors so the delivery queue can retry.
    """
    if not _validate_url(channel_kind, url):
        raise ValueError(f"{channel_kind} webhook URL rejected: {url!r}")

    if channel_kind == "discord":
        payload = _format_discord(banner, body, task_id, event)
    elif channel_kind == "slack":
        payload = _format_slack(banner, body, task_id, event)
    else:
        raise ValueError(f"unsupported channel_kind: {channel_kind}")

    data = json.dumps(payload).encode("utf-8")
    req = urllib.request.Request(
        url,
        data=data,
        headers={"Content-Type": "application/json", "User-Agent": "Mugi-Webhook/1"},
        method="POST",
    )
    opener = urllib.request.build_opener(_NoRedirectHandler)
    try:
        with opener.open(req, timeout=_TIMEOUT) as resp:
            resp.read(_MAX_RESPONSE + 1)
            _log.info("%s webhook delivered (%d): %s", channel_kind, resp.status, url[:80])
    except urllib.error.HTTPError as exc:
        _log.error("%s webhook HTTP %s for %s", channel_kind, exc.code, url[:80])
        raise


class _NoRedirectHandler(urllib.request.HTTPRedirectHandler):
    """Block HTTP redirects to prevent SSRF via open-redirect chains."""

    def redirect_request(self, req, fp, code, msg, headers, newurl):
        raise urllib.error.HTTPError(
            newurl, code, f"redirect blocked (SSRF protection): {newurl}", headers, fp
        )
