"""HTTP endpoint data model.

An ``HTTPEndpoint`` is a *connection definition*: where to fetch (URL),
how to authenticate (bearer token), what headers to send, and how long
to wait. It owns nothing about *what* to extract or *how often* to poll
— those concerns live on consumers (HTTPValueSource for periodic polling
+ extraction; potentially other consumers in future).

Mirrors the MQTT/HA source pattern (storage/mqtt_source.py,
storage/home_assistant_source.py).
"""

from dataclasses import dataclass, field
from datetime import datetime, timezone
from typing import Dict, List

from ledgrab.utils import secret_box


def _parse_common(data: dict) -> dict:
    """Extract common fields from a dict, parsing timestamps."""
    created = data.get("created_at", "")
    updated = data.get("updated_at", "")
    return {
        "id": data["id"],
        "name": data["name"],
        "created_at": (
            datetime.fromisoformat(created)
            if isinstance(created, str) and created
            else datetime.now(timezone.utc)
        ),
        "updated_at": (
            datetime.fromisoformat(updated)
            if isinstance(updated, str) and updated
            else datetime.now(timezone.utc)
        ),
        "description": data.get("description"),
        "tags": data.get("tags") or [],
    }


@dataclass
class HTTPEndpoint:
    """HTTP endpoint connection configuration."""

    id: str
    name: str
    created_at: datetime
    updated_at: datetime
    url: str = ""
    method: str = "GET"  # "GET" | "HEAD"
    auth_token: str = ""  # convenience: becomes Authorization: Bearer <token>
    headers: Dict[str, str] = field(default_factory=dict)
    timeout_s: float = 10.0
    description: str | None = None
    tags: List[str] = field(default_factory=list)
    icon: str = ""
    icon_color: str = ""

    def __post_init__(self) -> None:
        # Invariant: ``self.auth_token`` is always plaintext at runtime.
        # If a caller constructed this from a raw dict that still holds the
        # encrypted envelope, decrypt now so ``build_request_headers``
        # doesn't accidentally send ``Authorization: Bearer <envelope>``.
        if self.auth_token and secret_box.is_encrypted(self.auth_token):
            try:
                self.auth_token = secret_box.decrypt(self.auth_token)
            except Exception:
                self.auth_token = ""

    @property
    def plaintext_token(self) -> str:
        return secret_box.decrypt(self.auth_token) if self.auth_token else ""

    def build_request_headers(self) -> Dict[str, str]:
        """Compose the headers actually sent on a fetch.

        Order: explicit ``headers`` first, ``Authorization`` derived from
        ``auth_token`` only if the caller did not already supply one.
        The check is case-insensitive so a user-supplied ``authorization``
        (lower) wins over the bearer-token shortcut.
        """
        result: Dict[str, str] = dict(self.headers)
        already_has_auth = any(k.lower() == "authorization" for k in result)
        if self.auth_token and not already_has_auth:
            result["Authorization"] = f"Bearer {self.auth_token}"
        return result

    def to_dict(self) -> dict:
        # Always persist auth_token in encrypted envelope form. If the field
        # already contains an envelope, encrypt() is a no-op.
        stored_token = secret_box.encrypt(self.auth_token) if self.auth_token else ""
        d = {
            "id": self.id,
            "name": self.name,
            "url": self.url,
            "method": self.method,
            "auth_token": stored_token,
            "headers": dict(self.headers),
            "timeout_s": self.timeout_s,
            "description": self.description,
            "tags": self.tags,
            "created_at": self.created_at.isoformat(),
            "updated_at": self.updated_at.isoformat(),
        }
        if self.icon:
            d["icon"] = self.icon
        if self.icon_color:
            d["icon_color"] = self.icon_color
        return d

    @staticmethod
    def from_dict(data: dict) -> "HTTPEndpoint":
        common = _parse_common(data)
        raw_token = data.get("auth_token", "")
        token = secret_box.decrypt(raw_token) if raw_token else ""
        return HTTPEndpoint(
            **common,
            url=data.get("url", ""),
            method=data.get("method", "GET"),
            auth_token=token,
            headers=dict(data.get("headers") or {}),
            timeout_s=float(data.get("timeout_s", 10.0)),
            icon=data.get("icon", ""),
            icon_color=data.get("icon_color", ""),
        )
