"""Confluence page publisher (create design docs for Jira tickets)."""

from __future__ import annotations

from dataclasses import dataclass
from typing import List, Optional, Protocol

import httpx

from packages.artifacts.design import DesignArtifact
from packages.artifacts.requirements import RequirementsArtifact
from packages.artifacts.technical_design import (
    synthesize_technical_document,
    technical_design_title,
    technical_document_storage_html,
)
from packages.config import Settings, get_settings
from packages.integrations.sanitize import sanitize_text


@dataclass
class ConfluencePage:
    id: str
    title: str
    url: str


class ConfluencePublisher(Protocol):
    async def create_page(self, space_key: str, title: str, body_storage: str) -> ConfluencePage: ...


class MockConfluencePublisher:
    """In-memory Confluence publisher for local/dev and tests."""

    def __init__(self, base_url: str = "https://example.atlassian.net/wiki") -> None:
        self.base_url = base_url.rstrip("/")
        self.pages: List[ConfluencePage] = []
        self.creates: List[dict] = []
        self._next_id = 1000

    async def create_page(self, space_key: str, title: str, body_storage: str) -> ConfluencePage:
        page_id = str(self._next_id)
        self._next_id += 1
        safe_title = sanitize_text(title) or "Untitled"
        url = f"{self.base_url}/spaces/{space_key}/pages/{page_id}"
        page = ConfluencePage(id=page_id, title=safe_title, url=url)
        self.pages.append(page)
        self.creates.append(
            {
                "space_key": space_key,
                "title": safe_title,
                "body_storage": body_storage,
                "page": page,
            }
        )
        return page


class HttpConfluencePublisher:
    """Create Confluence Cloud pages via REST content API."""

    def __init__(
        self,
        settings: Optional[Settings] = None,
        transport: Optional[httpx.AsyncBaseTransport] = None,
    ) -> None:
        self.settings = settings or get_settings()
        self.transport = transport
        self._auth = (
            self.settings.confluence_email or self.settings.jira_email,
            self.settings.confluence_api_token or self.settings.jira_api_token,
        )

    async def create_page(self, space_key: str, title: str, body_storage: str) -> ConfluencePage:
        payload = {
            "type": "page",
            "title": sanitize_text(title) or "Untitled",
            "space": {"key": space_key},
            "body": {
                "storage": {
                    "value": body_storage,
                    "representation": "storage",
                }
            },
        }
        async with httpx.AsyncClient(
            timeout=30.0,
            auth=self._auth,
            transport=self.transport,
        ) as client:
            resp = await client.post(self._url("/rest/api/content"), json=payload)
            if resp.is_error:
                detail = _confluence_error_detail(resp)
                raise httpx.HTTPStatusError(
                    f"Confluence create_page failed ({resp.status_code}): {detail}",
                    request=resp.request,
                    response=resp,
                )
            data = resp.json()
        page_id = str(data.get("id") or "")
        page_title = sanitize_text(str(data.get("title") or title)) or "Untitled"
        url = _page_url(self.settings.confluence_base_url, data, page_id)
        if not url:
            raise RuntimeError("Confluence create_page response missing page URL")
        return ConfluencePage(id=page_id, title=page_title, url=url)

    def _url(self, path: str) -> str:
        return f"{self.settings.confluence_base_url.rstrip('/')}{path}"


_mock_publisher: Optional[MockConfluencePublisher] = None


def get_confluence_publisher(settings: Optional[Settings] = None) -> ConfluencePublisher:
    settings = settings or get_settings()
    if settings.confluence_provider == "http":
        return HttpConfluencePublisher(settings)
    global _mock_publisher
    if _mock_publisher is None:
        _mock_publisher = MockConfluencePublisher(base_url=settings.confluence_base_url)
    return _mock_publisher


def reset_mock_confluence_publisher() -> None:
    global _mock_publisher
    _mock_publisher = None


def design_doc_title(jira_key: str, summary: str, *, max_len: int = 180) -> str:
    """Confluence page title: Technical Design — {feature} ({jira_key})."""
    clean_summary = sanitize_text(summary or "").strip() or "Design"
    clean_key = sanitize_text(jira_key or "").strip()
    return technical_design_title(clean_summary, jira_key=clean_key, max_len=max_len)


def design_doc_storage_html(
    requirements: RequirementsArtifact,
    design: DesignArtifact,
) -> str:
    """Build Confluence storage-format HTML as a technical design document."""
    doc = synthesize_technical_document(requirements, design)
    # Keep page H1 aligned with the unique Confluence title (includes Jira key).
    doc.title = design_doc_title(design.jira_key or requirements.jira_key, design.summary or requirements.summary)
    return technical_document_storage_html(doc)


def _page_url(base_url: str, content: dict, page_id: str) -> Optional[str]:
    links = content.get("_links") or {}
    webui = links.get("webui")
    if webui:
        if webui.startswith("http://") or webui.startswith("https://"):
            return webui
        return f"{base_url.rstrip('/')}{webui}"
    if page_id:
        return f"{base_url.rstrip('/')}/spaces/pages/{page_id}"
    return None


def _confluence_error_detail(resp: httpx.Response) -> str:
    try:
        data = resp.json()
    except Exception:  # noqa: BLE001
        return (resp.text or resp.reason_phrase or "unknown error")[:500]
    message = data.get("message") or data.get("errorMessage") or ""
    if isinstance(message, str) and "BadRequestException: " in message:
        message = message.split("BadRequestException: ", 1)[-1]
    errors = data.get("errors") or data.get("data", {}).get("errors") or []
    if errors and not message:
        message = "; ".join(str(e) for e in errors)
    return (message or resp.text or resp.reason_phrase or "unknown error")[:500]
