package api

import (
	"io"
	"net/http"
	"reflect"
	"time"
)

// leafFlusherOf walks the Unwrap() chain on w (if any) all the way
// to the underlying leaf writer and reports whether that leaf
// actually implements http.Flusher. The returned Flusher, when ok is
// true, is the LEAF's Flusher — not an intermediate wrapper's.
//
// Why recursive, not single-level: gin's ResponseWriter satisfies
// http.Flusher unconditionally, and a custom wrapper can also satisfy
// it while wrapping a non-Flusher leaf. A single-level assertion
// would falsely pass and the gateway would commit a 200 + start an
// SSE response whose per-chunk Flush() lands on a no-op stub,
// keeping bytes buffered in the WRITER layer forever. Recursive
// unwrap to the leaf is the only way to tell whether real I/O will
// reach the connection.
//
// The walker treats both "no Unwrap() method" and "Unwrap() returns
// nil" as terminal; either way the current writer is the leaf.
//
// Fail-closed semantics: if the Unwrap chain cycles (A→B→A→B…) or
// exceeds maxUnwrapDepth, the helper returns (nil, false). Returning
// the cyclic/over-deep writer's Flusher would let a no-op stub
// masquerade as the leaf and the gateway would commit a 200 whose
// per-chunk Flush lands on the fake. The only safe answer is "no
// proven Flusher → reject pre-commit with 5xx".
func leafFlusherOf(w http.ResponseWriter) (http.Flusher, bool) {
	if w == nil {
		return nil, false
	}
	const maxUnwrapDepth = 64
	seen := make(map[http.ResponseWriter]struct{}, maxUnwrapDepth+1)
	for i := 0; i < maxUnwrapDepth; i++ {
		// An interface value is only a valid map key when its dynamic type
		// is comparable. Non-comparable writer values are still bounded by
		// maxUnwrapDepth; comparable writers additionally get exact cycle
		// detection.
		if writerType := reflect.TypeOf(w); writerType != nil && writerType.Comparable() {
			if _, dup := seen[w]; dup {
				return nil, false
			}
			seen[w] = struct{}{}
		}
		unwrap, ok := w.(interface{ Unwrap() http.ResponseWriter })
		if !ok {
			break
		}
		next := unwrap.Unwrap()
		if next == nil {
			break
		}
		w = next
		if i == maxUnwrapDepth-1 {
			return nil, false
		}
	}
	flusher, ok := w.(http.Flusher)
	return flusher, ok
}

// maybeSetReadDeadline invokes the underlying connection's read
// deadline via http.NewResponseController. The gateway uses this on
// entry.Context().Done() to interrupt an in-flight io.ReadAll of the
// request body — a non-blocking alternative to *http.body.Close,
// which contends for the body mutex and may synchronously drain up
// to 256KiB before returning.
//
// Returns nil when the underlying writer honors read deadlines.
// Returns http.ErrNotSupported (or a wrapper) when it does not; the
// gateway MUST treat that as "operation cannot be force-interrupted"
// and fall back to bounded shutdown (CancelAll's wait ctx will time
// out and the daemon stays Preparing).
func maybeSetReadDeadline(w http.ResponseWriter, deadline time.Time) error {
	return http.NewResponseController(w).SetReadDeadline(deadline)
}

// copySSEBody preserves ordinary streaming backpressure while flushing every
// successful downstream write. It deliberately flushes writes rather than
// parsed SSE events: upstream transports are free to split or coalesce reads,
// and flushing each write guarantees every byte-producing read is made visible
// promptly without buffering the complete stream.
func copySSEBody(dst io.Writer, src io.Reader, flusher http.Flusher) (int64, error) {
	buf := make([]byte, 32*1024)
	var written int64
	for {
		nr, readErr := src.Read(buf)
		if nr > 0 {
			nw, writeErr := dst.Write(buf[:nr])
			written += int64(nw)
			if writeErr != nil {
				return written, writeErr
			}
			if nw != nr {
				return written, io.ErrShortWrite
			}
			flusher.Flush()
		}
		if readErr != nil {
			if readErr == io.EOF {
				return written, nil
			}
			return written, readErr
		}
	}
}
