"""HTTP endpoint schemas (CRUD + one-shot test)."""

import re
from datetime import datetime
from typing import Any, Dict, List, Literal
from urllib.parse import urlparse

from pydantic import BaseModel, Field, field_validator

# RFC 7230 token chars for header names + reject any control character in values.
_HEADER_NAME_RE = re.compile(r"^[A-Za-z0-9!#$%&'*+\-.^_`|~]+$")
_HEADER_CONTROL_CHARS_RE = re.compile(r"[\x00-\x1f\x7f]")


def _validate_headers(value: Dict[str, str]) -> Dict[str, str]:
    """Reject header names/values that could enable CRLF injection."""
    if value is None:
        return {}
    cleaned: Dict[str, str] = {}
    for name, raw in value.items():
        if not isinstance(name, str) or not isinstance(raw, str):
            raise ValueError(f"Header name/value must be strings: {name!r}")
        if not _HEADER_NAME_RE.match(name):
            raise ValueError(f"Invalid HTTP header name: {name!r}")
        if _HEADER_CONTROL_CHARS_RE.search(raw):
            raise ValueError(
                f"Invalid HTTP header value for {name!r} (contains control characters)"
            )
        cleaned[name] = raw
    return cleaned


def _validate_url(value: str) -> str:
    """Reject URLs that embed ``user:pass@`` so credentials can't leak
    into server logs (e.g. via the plaintext-token warning helper).

    Schemes + IP-block checks are enforced later by
    :func:`ledgrab.utils.safe_source.validate_polling_url`.
    """
    if not value:
        return value
    parsed = urlparse(value)
    if parsed.username is not None or parsed.password is not None:
        raise ValueError(
            "URL must not embed credentials (user:pass@). "
            "Use the auth_token field or a custom Authorization header instead."
        )
    return value


class HTTPEndpointCreate(BaseModel):
    """Request to create an HTTP endpoint."""

    name: str = Field(min_length=1, max_length=100)
    url: str = Field(min_length=1, description="http or https URL")
    method: Literal["GET", "HEAD"] = "GET"
    auth_token: str = Field(
        default="",
        description=(
            "Optional bearer token — sent as 'Authorization: Bearer <token>'. "
            "Add a custom Authorization entry in `headers` to override."
        ),
    )
    headers: Dict[str, str] = Field(default_factory=dict)
    timeout_s: float = Field(default=10.0, gt=0)
    description: str | None = Field(None, max_length=500)
    tags: List[str] = Field(default_factory=list)
    icon: str | None = Field(None, max_length=64)
    icon_color: str | None = Field(None, max_length=32)

    @field_validator("headers")
    @classmethod
    def _check_headers(cls, value):
        return _validate_headers(value)

    @field_validator("url")
    @classmethod
    def _check_url(cls, value):
        return _validate_url(value)


class HTTPEndpointUpdate(BaseModel):
    """Request to update an HTTP endpoint.

    All fields optional — ``None`` keeps the existing value. Sending an
    empty string for ``auth_token`` CLEARS the stored token; omit the
    field (or send ``null``) to keep it.
    """

    name: str | None = Field(None, min_length=1, max_length=100)
    url: str | None = Field(None, min_length=1)
    method: Literal["GET", "HEAD"] | None = None
    auth_token: str | None = Field(None, description="null = keep existing; '' = clear.")
    headers: Dict[str, str] | None = None
    timeout_s: float | None = Field(None, gt=0)
    description: str | None = Field(None, max_length=500)
    tags: List[str] | None = None
    icon: str | None = Field(None, max_length=64)
    icon_color: str | None = Field(None, max_length=32)

    @field_validator("headers")
    @classmethod
    def _check_headers(cls, value):
        if value is None:
            return None
        return _validate_headers(value)

    @field_validator("url")
    @classmethod
    def _check_url(cls, value):
        if value is None:
            return None
        return _validate_url(value)


class HTTPEndpointResponse(BaseModel):
    """HTTP endpoint response. Note: ``auth_token`` is NEVER returned —
    use ``auth_token_set`` to know whether one is configured."""

    id: str
    name: str
    url: str
    method: str
    auth_token_set: bool = False
    headers: Dict[str, str] = Field(default_factory=dict)
    timeout_s: float
    description: str | None = None
    tags: List[str] = Field(default_factory=list)
    icon: str | None = Field(None, max_length=64)
    icon_color: str | None = Field(None, max_length=32)
    created_at: datetime
    updated_at: datetime


class HTTPEndpointListResponse(BaseModel):
    endpoints: List[HTTPEndpointResponse]
    count: int


class HTTPTestRequest(BaseModel):
    """One-shot test request to validate URL + auth before saving."""

    url: str
    method: Literal["GET", "HEAD"] = "GET"
    auth_token: str = ""
    headers: Dict[str, str] = Field(default_factory=dict)
    timeout_s: float = Field(default=10.0, gt=0)

    @field_validator("headers")
    @classmethod
    def _check_headers(cls, value):
        return _validate_headers(value)

    @field_validator("url")
    @classmethod
    def _check_url(cls, value):
        return _validate_url(value)


class HTTPTestResponse(BaseModel):
    success: bool
    status_code: int | None = None
    body_preview: str | None = Field(None, description="First 500 chars of the body")
    body_json: Any = None
    error: str | None = None
