"""HTTP endpoint routes: CRUD + one-shot test."""

import json

from fastapi import APIRouter, Depends, HTTPException

from ledgrab.api.auth import AuthRequired
from ledgrab.api.dependencies import (
    fire_entity_event,
    get_http_endpoint_store,
)
from ledgrab.api.schemas.http_endpoints import (
    HTTPEndpointCreate,
    HTTPEndpointListResponse,
    HTTPEndpointResponse,
    HTTPEndpointUpdate,
    HTTPTestRequest,
    HTTPTestResponse,
)
from ledgrab.storage.base_store import EntityNotFoundError
from ledgrab.storage.http_endpoint import HTTPEndpoint
from ledgrab.storage.http_endpoint_store import HTTPEndpointStore
from ledgrab.utils import get_logger
from ledgrab.utils.safe_source import safe_request_bounded, validate_polling_url

logger = get_logger(__name__)

router = APIRouter()


def _warn_if_plaintext_token(url: str, auth_token: str, *, action: str) -> None:
    """Log a warning when an auth token would be sent over plaintext http://."""
    if auth_token and url.lower().startswith("http://"):
        logger.warning(
            "HTTP endpoint %s: auth_token will be sent over plaintext http:// to %s. "
            "Anyone on the network path can read it. Consider https:// if the "
            "target supports TLS.",
            action,
            url,
        )


def _to_response(endpoint: HTTPEndpoint) -> HTTPEndpointResponse:
    return HTTPEndpointResponse(
        id=endpoint.id,
        name=endpoint.name,
        url=endpoint.url,
        method=endpoint.method,
        auth_token_set=bool(endpoint.auth_token),
        headers=dict(endpoint.headers),
        timeout_s=endpoint.timeout_s,
        description=endpoint.description,
        tags=endpoint.tags,
        icon=getattr(endpoint, "icon", "") or "",
        icon_color=getattr(endpoint, "icon_color", "") or "",
        created_at=endpoint.created_at,
        updated_at=endpoint.updated_at,
    )


@router.get(
    "/api/v1/http/endpoints",
    response_model=HTTPEndpointListResponse,
    tags=["HTTP"],
)
async def list_http_endpoints(
    _auth: AuthRequired,
    store: HTTPEndpointStore = Depends(get_http_endpoint_store),
):
    endpoints = store.get_all_endpoints()
    return HTTPEndpointListResponse(
        endpoints=[_to_response(e) for e in endpoints],
        count=len(endpoints),
    )


@router.post(
    "/api/v1/http/endpoints",
    response_model=HTTPEndpointResponse,
    status_code=201,
    tags=["HTTP"],
)
async def create_http_endpoint(
    data: HTTPEndpointCreate,
    _auth: AuthRequired,
    store: HTTPEndpointStore = Depends(get_http_endpoint_store),
):
    validate_polling_url(data.url)
    _warn_if_plaintext_token(data.url, data.auth_token, action="create")
    try:
        endpoint = store.create_endpoint(
            name=data.name,
            url=data.url,
            method=data.method,
            auth_token=data.auth_token,
            headers=data.headers,
            timeout_s=data.timeout_s,
            description=data.description,
            tags=data.tags,
            icon=data.icon,
            icon_color=data.icon_color,
        )
    except ValueError as e:
        raise HTTPException(status_code=400, detail=str(e))
    fire_entity_event("http_endpoint", "created", endpoint.id)
    return _to_response(endpoint)


@router.get(
    "/api/v1/http/endpoints/{endpoint_id}",
    response_model=HTTPEndpointResponse,
    tags=["HTTP"],
)
async def get_http_endpoint(
    endpoint_id: str,
    _auth: AuthRequired,
    store: HTTPEndpointStore = Depends(get_http_endpoint_store),
):
    try:
        endpoint = store.get_endpoint(endpoint_id)
    except EntityNotFoundError:
        raise HTTPException(status_code=404, detail=f"HTTP endpoint {endpoint_id} not found")
    return _to_response(endpoint)


@router.put(
    "/api/v1/http/endpoints/{endpoint_id}",
    response_model=HTTPEndpointResponse,
    tags=["HTTP"],
)
async def update_http_endpoint(
    endpoint_id: str,
    data: HTTPEndpointUpdate,
    _auth: AuthRequired,
    store: HTTPEndpointStore = Depends(get_http_endpoint_store),
):
    if data.url is not None:
        validate_polling_url(data.url)
    final_url = data.url
    final_token = data.auth_token
    if final_url is None or final_token is None:
        try:
            existing = store.get_endpoint(endpoint_id)
        except EntityNotFoundError:
            raise HTTPException(status_code=404, detail=f"HTTP endpoint {endpoint_id} not found")
        if final_url is None:
            final_url = existing.url
        if final_token is None:
            final_token = existing.auth_token
    _warn_if_plaintext_token(final_url, final_token, action="update")
    try:
        endpoint = store.update_endpoint(
            endpoint_id,
            name=data.name,
            url=data.url,
            method=data.method,
            auth_token=data.auth_token,
            headers=data.headers,
            timeout_s=data.timeout_s,
            description=data.description,
            tags=data.tags,
            icon=data.icon,
            icon_color=data.icon_color,
        )
    except EntityNotFoundError:
        raise HTTPException(status_code=404, detail=f"HTTP endpoint {endpoint_id} not found")
    except ValueError as e:
        raise HTTPException(status_code=400, detail=str(e))
    fire_entity_event("http_endpoint", "updated", endpoint.id)
    return _to_response(endpoint)


@router.delete(
    "/api/v1/http/endpoints/{endpoint_id}",
    status_code=204,
    tags=["HTTP"],
)
async def delete_http_endpoint(
    endpoint_id: str,
    _auth: AuthRequired,
    store: HTTPEndpointStore = Depends(get_http_endpoint_store),
):
    try:
        store.delete_endpoint(endpoint_id)
    except EntityNotFoundError:
        raise HTTPException(status_code=404, detail=f"HTTP endpoint {endpoint_id} not found")
    fire_entity_event("http_endpoint", "deleted", endpoint_id)


async def _run_http_test(
    method: str,
    url: str,
    headers: dict[str, str],
    timeout_s: float,
) -> HTTPTestResponse:
    """Shared one-shot fetch + response shaping for both test endpoints."""
    try:
        status, body_bytes, error = await safe_request_bounded(
            method, url, headers=headers, timeout=timeout_s
        )
    except HTTPException:
        raise
    except Exception as exc:
        return HTTPTestResponse(success=False, error=f"Unexpected error: {type(exc).__name__}")

    if error and status == 0:
        return HTTPTestResponse(success=False, error=error)

    try:
        body_text = body_bytes.decode("utf-8")
    except UnicodeDecodeError:
        body_text = body_bytes.decode("utf-8", errors="replace")
    try:
        body_json = json.loads(body_text) if body_text else None
    except (json.JSONDecodeError, ValueError):
        body_json = None

    preview = body_text[:500] if body_text else None
    is_success = 200 <= status < 300
    return HTTPTestResponse(
        success=is_success,
        status_code=status,
        body_preview=preview,
        body_json=body_json,
        error=None if is_success else f"HTTP {status}",
    )


@router.post(
    "/api/v1/http/endpoints/test",
    response_model=HTTPTestResponse,
    tags=["HTTP"],
)
async def test_http_endpoint(
    data: HTTPTestRequest,
    _auth: AuthRequired,
):
    """One-shot fetch to validate URL + auth before saving."""
    headers = dict(data.headers)
    if data.auth_token and not any(k.lower() == "authorization" for k in headers):
        headers["Authorization"] = f"Bearer {data.auth_token}"
    return await _run_http_test(data.method, data.url, headers, data.timeout_s)


@router.post(
    "/api/v1/http/endpoints/{endpoint_id}/test",
    response_model=HTTPTestResponse,
    tags=["HTTP"],
)
async def test_saved_http_endpoint(
    endpoint_id: str,
    _auth: AuthRequired,
    store: HTTPEndpointStore = Depends(get_http_endpoint_store),
):
    """Run the stored endpoint configuration (URL + auth + headers + timeout).

    Useful for the "test" button on the endpoint card: avoids the user
    having to open the editor and re-enter the auth token (which is
    never returned to the client).
    """
    try:
        endpoint = store.get_endpoint(endpoint_id)
    except EntityNotFoundError:
        raise HTTPException(status_code=404, detail=f"HTTP endpoint {endpoint_id} not found")
    return await _run_http_test(
        endpoint.method,
        endpoint.url,
        endpoint.build_request_headers(),
        endpoint.timeout_s,
    )
