from __future__ import annotations

import json
import logging
from dataclasses import dataclass, field
from typing import Any
from urllib import error, parse, request

from app.config import Settings

logger = logging.getLogger(__name__)
SUCCESS_STATUS_CODES = {200, 201}


class HomeAssistantConfigError(RuntimeError):
    """Raised when required Home Assistant outbound configuration is missing."""


class HomeAssistantRequestError(RuntimeError):
    """Raised when a Home Assistant outbound HTTP request fails."""


@dataclass(slots=True)
class HomeAssistantClient:
    settings: Settings
    timeout_seconds: float | None = field(default=None)

    def __post_init__(self) -> None:
        if self.timeout_seconds is None:
            self.timeout_seconds = self.settings.home_assistant_timeout_seconds

    def is_configured(self) -> bool:
        return bool(self.settings.home_assistant_base_url and self.settings.home_assistant_auth_token)

    def publish_sensor(
        self,
        *,
        entity_id: str,
        state: str,
        attributes: dict[str, Any] | None = None,
    ) -> None:
        self._require_config()
        if not entity_id:
            raise ValueError("entity_id must not be empty")

        payload = {
            "entity_id": entity_id,
            "state": state,
            "attributes": attributes or {},
        }
        self._post_json(f"/api/states/{entity_id}", payload, operation="publish_sensor")

    def trigger_webhook(self, *, webhook_id: str, body: Any) -> None:
        self._require_config()
        if not webhook_id:
            raise ValueError("webhook_id must not be empty")

        self._post_json(f"/api/webhook/{webhook_id}", body, operation="trigger_webhook")

    def _require_config(self) -> None:
        if self.is_configured():
            return
        raise HomeAssistantConfigError(
            "Home Assistant outbound integration is not configured. "
            "Set HOME_ASSISTANT_BASE_URL and HOME_ASSISTANT_AUTH_TOKEN."
        )

    def _post_json(self, path: str, payload: Any, *, operation: str) -> None:
        url = self._build_url(path)
        body = json.dumps(payload).encode("utf-8")
        req = request.Request(url, data=body, method="POST")
        req.add_header("Content-Type", "application/json")
        req.add_header("Authorization", f"Bearer {self.settings.home_assistant_auth_token}")

        try:
            with request.urlopen(req, timeout=self.timeout_seconds) as response:
                status_code = response.getcode()
        except error.HTTPError as exc:
            logger.warning(
                "Home Assistant outbound %s failed with HTTP %s for %s",
                operation,
                exc.code,
                url,
            )
            raise HomeAssistantRequestError(
                f"Home Assistant outbound {operation} failed with HTTP {exc.code}"
            ) from exc
        except error.URLError as exc:
            logger.warning("Home Assistant outbound %s failed for %s: %s", operation, url, exc)
            raise HomeAssistantRequestError(
                f"Home Assistant outbound {operation} failed to reach Home Assistant"
            ) from exc

        if status_code not in SUCCESS_STATUS_CODES:
            logger.warning(
                "Home Assistant outbound %s returned unexpected status %s for %s",
                operation,
                status_code,
                url,
            )
            raise HomeAssistantRequestError(
                f"Home Assistant outbound {operation} returned unexpected status {status_code}"
            )

    def _build_url(self, path: str) -> str:
        base_url = self.settings.home_assistant_base_url.rstrip("/")
        quoted_path = parse.quote(path.lstrip("/"), safe="/")
        return f"{base_url}/{quoted_path}"
