package openai_compat

import (
	"context"
	"fmt"
	"io"
	"net/http"
	"strings"
	"time"

	runtime "iop/packages/go/execution"
)

// TunnelProvider relays an arbitrary HTTP request to the OpenAI-compatible
// endpoint and streams the response back through the sink.
func (a *Adapter) TunnelProvider(ctx context.Context, req runtime.ProviderTunnelRequest, sink runtime.ProviderTunnelSink) error {
	var seq int64 = 0

	if a.endpoint == "" && a.profile == nil {
		err := fmt.Errorf("openai_compat adapter: endpoint or profile is required")
		_ = emitTunnelError(ctx, sink, req, seq, err)
		return err
	}

	isJSON := req.Method == http.MethodPost
	httpReq, err := a.prepareRequest(ctx, req.Method, req.Operation, req.Path, req.Body, req.Headers, isJSON)
	if err != nil {
		_ = emitTunnelError(ctx, sink, req, seq, err)
		return err
	}
	if req.Credential != nil {
		defer req.Credential.Zero()
		for header := range httpReq.Header {
			if strings.EqualFold(header, req.Credential.HeaderName) {
				err := fmt.Errorf("provider credential header collision")
				_ = emitTunnelError(ctx, sink, req, seq, err)
				return err
			}
		}
		value := string(req.Credential.Secret)
		if scheme := strings.TrimSpace(req.Credential.Scheme); scheme != "" {
			value = scheme + " " + value
		}
		httpReq.Header.Set(req.Credential.HeaderName, value)
		defer httpReq.Header.Del(req.Credential.HeaderName)
	}

	resp, err := a.client.Do(httpReq)
	if err != nil {
		err = fmt.Errorf("request failed: %w", err)
		_ = emitTunnelError(ctx, sink, req, seq, err)
		return err
	}
	defer resp.Body.Close()

	respHeaders := make(map[string]string)
	for k, values := range resp.Header {
		if len(values) > 0 {
			respHeaders[k] = values[0]
		}
	}

	err = sink.EmitTunnelFrame(ctx, runtime.ProviderTunnelFrame{
		RunID:      req.RunID,
		TunnelID:   req.TunnelID,
		Sequence:   seq,
		Kind:       runtime.ProviderTunnelFrameKindResponseStart,
		StatusCode: resp.StatusCode,
		Headers:    respHeaders,
		Timestamp:  time.Now(),
	})
	if err != nil {
		return err
	}
	seq++

	buf := make([]byte, 4096)
	for {
		n, readErr := resp.Body.Read(buf)
		if n > 0 {
			err = sink.EmitTunnelFrame(ctx, runtime.ProviderTunnelFrame{
				RunID:     req.RunID,
				TunnelID:  req.TunnelID,
				Sequence:  seq,
				Kind:      runtime.ProviderTunnelFrameKindBody,
				Body:      append([]byte(nil), buf[:n]...),
				Timestamp: time.Now(),
			})
			if err != nil {
				return err
			}
			seq++
		}
		if readErr != nil {
			if readErr == io.EOF {
				break
			}
			if ctx.Err() != nil {
				_ = emitTunnelError(ctx, sink, req, seq, ctx.Err())
				return ctx.Err()
			}
			err = fmt.Errorf("read response body: %w", readErr)
			_ = emitTunnelError(ctx, sink, req, seq, err)
			return err
		}
	}

	err = sink.EmitTunnelFrame(ctx, runtime.ProviderTunnelFrame{
		RunID:     req.RunID,
		TunnelID:  req.TunnelID,
		Sequence:  seq,
		Kind:      runtime.ProviderTunnelFrameKindEnd,
		End:       true,
		Timestamp: time.Now(),
	})
	return err
}

func emitTunnelError(ctx context.Context, sink runtime.ProviderTunnelSink, req runtime.ProviderTunnelRequest, seq int64, err error) error {
	return sink.EmitTunnelFrame(ctx, runtime.ProviderTunnelFrame{
		RunID:     req.RunID,
		TunnelID:  req.TunnelID,
		Sequence:  seq,
		Kind:      runtime.ProviderTunnelFrameKindError,
		Error:     err.Error(),
		Timestamp: time.Now(),
	})
}
