"""HTTP client for the Ansel API server."""

from __future__ import annotations

import os
import sys
from collections.abc import Callable
from dataclasses import dataclass, field
from typing import cast

import httpx
from httpx_sse import connect_sse

from .auth import get_identity_headers
from .parse import JSONObject, SDKEvent, parse_sdk_event, parse_sse_event

API_BASE = os.environ.get('ANSEL_API_BASE', 'https://api.ansel.sh')

EventCallback = Callable[[SDKEvent, 'StreamResult'], None]


@dataclass
class StreamResult:
    """Collected result from an SSE stream with JSONL SDK events."""

    events: list[SDKEvent] = field(default_factory=list)
    stderr: str = ''
    exit_code: int | None = None
    errors: list[str] = field(default_factory=list)
    run_id: str | None = None

    def get_event(self, name: str) -> SDKEvent | None:
        """Return the last event with the given name, or None."""
        for ev in reversed(self.events):
            if ev['event'] == name:
                return ev
        return None


def post_stream(
    path: str,
    token: str,
    *,
    content: str = '',
    files: list[tuple[str, str, str]] | None = None,
    json_body: JSONObject | None = None,
    params: dict[str, str] | None = None,
    verbose: bool = False,
    on_event: EventCallback | None = None,
) -> StreamResult:
    """POST to an endpoint and consume the SSE stream.

    SDK JSONL events (stdout) are collected as structured data.
    Stderr is suppressed unless verbose=True.

    When ``on_event`` is provided, it is called immediately as each SDK
    event arrives — before the stream completes. Use this to print
    progress so the caller sees output during long-running operations.

    When ``files`` is provided, sends a multipart request. Each tuple is
    ``(field_name, filename, content)``.

    When ``json_body`` is provided, sends a JSON request.
    """
    result = StreamResult()

    # Build kwargs for connect_sse — multipart files, JSON body, or raw content
    extra_kwargs: dict[str, object] = {}
    if files:
        extra_kwargs['files'] = [(name, (fname, data, 'text/csv')) for name, fname, data in files]
    elif json_body is not None:
        extra_kwargs['json'] = json_body
    else:
        extra_kwargs['content'] = content

    try:
        with httpx.Client(timeout=httpx.Timeout(connect=30, read=600, write=30, pool=30)) as client:  # noqa: SIM117
            with connect_sse(
                client,
                'POST',
                f'{API_BASE}{path}',
                params=params or {},
                headers={'Authorization': f'Bearer {token}', **get_identity_headers()},
                **extra_kwargs,
            ) as sse:
                # Check for non-SSE error responses (e.g. Worker timeout, HTML error page)
                ct: object = sse.response.headers.get('content-type', '')  # pyright: ignore[reportAny]
                content_type: str = str(ct)
                if 'text/event-stream' not in content_type:
                    status = sse.response.status_code
                    result.errors.append(f'server returned {status} ({content_type})')
                    result.exit_code = 1
                    return result

                result.run_id = sse.response.headers.get('x-run-id')

                for sse_event in sse.iter_sse():
                    event = parse_sse_event(sse_event.data)
                    if event is None:
                        continue

                    if event['type'] == 'stderr':
                        result.stderr += event['data']
                        if verbose:
                            _ = sys.stderr.write(event['data'])
                            _ = sys.stderr.flush()

                    elif event['type'] == 'stdout':
                        for line in event['data'].splitlines():
                            sdk_event = parse_sdk_event(line)
                            if sdk_event is not None:
                                result.events.append(sdk_event)
                                if on_event is not None:
                                    on_event(sdk_event, result)

                    elif event['type'] == 'complete':
                        result.exit_code = event['exitCode']

                    elif event['type'] == 'error':
                        result.errors.append(event['message'])
    except httpx.ConnectError as e:
        result.errors.append(f'connection failed: {e}')
        result.exit_code = 1
    except Exception as e:
        result.errors.append(str(e))
        result.exit_code = 1

    return result


def post_resume(
    command: str,
    run_id: str,
    token: str,
    *,
    on_event: EventCallback | None = None,
) -> StreamResult:
    """POST to a resume endpoint and consume the SSE stream.

    Hits ``/<command>/resume/<run_id>`` with the same streaming
    behavior as ``post_stream``.
    """
    return post_stream(
        f'/{command}/resume/{run_id}',
        token,
        on_event=on_event,
    )


def get_json(token: str, path: str) -> JSONObject | None:
    """GET a JSON endpoint. Returns parsed response or None on error."""
    try:
        resp = httpx.get(
            f'{API_BASE}{path}',
            headers={'Authorization': f'Bearer {token}', **get_identity_headers()},
            timeout=30,
        )
        if resp.status_code == 404:
            return None
        _ = resp.raise_for_status()
        result: object = resp.json()  # pyright: ignore[reportAny]
        if isinstance(result, dict):
            return cast(JSONObject, result)
        return None
    except httpx.HTTPError:
        return None
