import asyncio
import json
import urllib.error
import urllib.request
from typing import Any

from app.settings import settings
from app.utils.logger import get_logger

logger = get_logger(__name__)


def _notifications_enabled() -> bool:
    return bool(settings.SLACK_ERROR_NOTIFICATIONS_ENABLED) and bool(
        settings.SLACK_ERROR_WEBHOOK_URL
    )


def _should_notify() -> bool:
    return _notifications_enabled()


def _sanitize(text: str | None) -> str | None:
    if text is None:
        return None
    return text.replace("&", "&amp;").replace("<", "&lt;").replace(">", "&gt;")


def _truncate(text: str | None, limit: int) -> str | None:
    if text is None:
        return None
    return text if len(text) <= limit else f"{text[:limit]}..."


def _compact_dict(data: dict[str, Any]) -> dict[str, Any]:
    return {key: value for key, value in data.items() if value is not None}


def _json_block(data: dict[str, Any], limit: int = 2800) -> str:
    text = json.dumps(_compact_dict(data), indent=2)
    return _truncate(text, limit) or ""


def _build_payload(context: dict[str, Any]) -> dict[str, Any]:
    title = (
        _sanitize(_truncate(context.get("title", "Unhandled error"), 120))
        or "Unhandled error"
    )
    message = (
        _sanitize(_truncate(context.get("message", "Internal Server Error"), 3000))
        or "Internal Server Error"
    )
    level = "ERROR"

    blocks = [
        {
            "type": "header",
            "text": {
                "type": "plain_text",
                "text": title,
                "emoji": True,
            },
        },
        {
            "type": "section",
            "text": {
                "type": "mrkdwn",
                "text": "\n\n".join(
                    line
                    for line in [
                        f"*Message:*\n{message}",
                        f"*Level:*\n{level}",
                        (
                            f"*requestId:*\n{_sanitize(str(context.get('request_id')))}"
                            if context.get("request_id")
                            else None
                        ),
                    ]
                    if line
                ),
            },
        },
    ]

    context_block = _compact_dict(
        {
            "requestId": context.get("request_id"),
            "path": _sanitize(context.get("path")),
            "method": context.get("method"),
            "statusCode": context.get("status_code"),
            "userAgent": _sanitize(context.get("user_agent")),
            "clientIp": context.get("client_ip"),
        }
    )

    blocks.append(
        {
            "type": "section",
            "text": {
                "type": "mrkdwn",
                "text": f"*Context*\n```{_json_block(context_block)}```",
            },
        }
    )

    exception_block = _compact_dict(
        {
            "name": context.get("error_name"),
            "message": _sanitize(_truncate(context.get("error_message"), 4000)),
            "stack": _truncate(context.get("error_stack"), 12000),
        }
    )

    blocks.append(
        {
            "type": "section",
            "text": {
                "type": "mrkdwn",
                "text": f"*Exception*\n```{_json_block(exception_block)}```",
            },
        }
    )

    return {"text": title, "blocks": blocks}


def _post_to_slack(payload: dict[str, Any]) -> None:
    webhook_url = settings.SLACK_ERROR_WEBHOOK_URL
    if not webhook_url:
        return
    body = json.dumps(payload).encode("utf-8")
    request = urllib.request.Request(
        webhook_url,
        data=body,
        headers={"Content-Type": "application/json"},
        method="POST",
    )
    try:
        with urllib.request.urlopen(request, timeout=5) as response:
            if response.status >= 400:
                raise RuntimeError(f"Slack webhook failed with {response.status}")
    except urllib.error.HTTPError as exc:
        details = exc.read().decode("utf-8", errors="replace")
        raise RuntimeError(f"Slack webhook failed with {exc.code}: {details}") from exc


async def notify_error(context: dict[str, Any]) -> None:
    if not _should_notify():
        return
    payload = _build_payload(context)
    try:
        await asyncio.to_thread(_post_to_slack, payload)
    except Exception as exc:
        logger.error("Slack error notification failed: %s", exc)
