package oci

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

	"github.com/oracle/oci-go-sdk/v65/common"
)

// compatResponsesLimit 限制直通响应体大小;web_search 输出含多段引用,给足余量。
const compatResponsesLimit = int64(8 << 20)

// dispatcherWithTimeout 把 dispatcher 换成指定总超时的拷贝(保留 Transport,
// 代理链路不受影响);timeout=0 表示无总超时(流式读 body 不能有总时限)。
// 非 *http.Client 的自定义 dispatcher 保持原样,维持既有超时行为。
func dispatcherWithTimeout(d common.HTTPRequestDispatcher, timeout time.Duration) common.HTTPRequestDispatcher {
	hc, ok := d.(*http.Client)
	if !ok {
		return d
	}
	return &http.Client{Transport: hc.Transport, Timeout: timeout}
}

// newCompatResponsesRequest 构造 /actions/v1/responses 直通请求。
func newCompatResponsesRequest(ctx context.Context, cred Credentials, body []byte) (*http.Request, error) {
	request, err := http.NewRequestWithContext(ctx, http.MethodPost, "/actions/v1/responses", bytes.NewReader(body))
	if err != nil {
		return nil, fmt.Errorf("build compat responses request: %w", err)
	}
	request.Header.Set("Content-Type", "application/json")
	request.Header.Set("CompartmentId", cred.TenancyOCID)
	request.Header.Set("opc-compartment-id", cred.TenancyOCID)
	return request, nil
}

// GenAiCompatResponses 实现 Client:把 OpenAI Responses 请求体直通到 OCI
// `/20231130/actions/v1/responses`(IAM 签名)。xAI 服务端工具(web_search /
// x_search / code_interpreter)与 mcp 已被 Oracle 文档正式支持,工具参数与限制
// 遵循 xAI 规格;调用方须自行校验并改写请求体(store/stream)。
// wait 为整请求总超时:非流式上游要等全部生成完才回响应头,SDK 默认 60s 会掐断慢模型。
func (c *RealClient) GenAiCompatResponses(ctx context.Context, cred Credentials, region string, body []byte, wait time.Duration) ([]byte, error) {
	ic, err := c.genAiInferenceClient(cred, region)
	if err != nil {
		return nil, err
	}
	client := ic.BaseClient
	common.UpdateEndpointTemplateForOptions(&client)
	common.SetMissingTemplateParams(&client)
	client.HTTPClient = dispatcherWithTimeout(client.HTTPClient, wait)
	request, err := newCompatResponsesRequest(ctx, cred, body)
	if err != nil {
		return nil, err
	}
	response, err := client.Call(ctx, request)
	if err != nil {
		return nil, err
	}
	defer response.Body.Close()
	payload, err := io.ReadAll(io.LimitReader(response.Body, compatResponsesLimit))
	if err != nil {
		return nil, fmt.Errorf("read compat responses body: %w", err)
	}
	return payload, nil
}

// cancelReadCloser 在流关闭时同步取消建立阶段派生的 ctx,避免其随流生命周期泄漏。
type cancelReadCloser struct {
	io.ReadCloser
	cancel context.CancelFunc
}

func (c *cancelReadCloser) Close() error {
	c.cancel()
	return c.ReadCloser.Close()
}

// httpCaller 抽象 BaseClient.Call,便于对预算逻辑做无签名单测。
type httpCaller interface {
	Call(ctx context.Context, request *http.Request) (*http.Response, error)
}

// callWithHeaderBudget 以 wait 为等待响应头预算执行调用:预算内未返回则取消
// 请求(SDK Call 会把 ctx 重绑到请求);响应头到达即解除预算,之后流的生命
// 周期由 ctx 决定,返回的流 Close 时同步取消派生 ctx。
func callWithHeaderBudget(ctx context.Context, c httpCaller, req *http.Request, wait time.Duration) (io.ReadCloser, error) {
	callCtx, cancel := context.WithCancel(ctx)
	timer := time.AfterFunc(wait, cancel)
	response, err := c.Call(callCtx, req)
	timer.Stop()
	if err != nil {
		if response != nil && response.Body != nil {
			response.Body.Close()
		}
		cancel()
		return nil, err
	}
	return &cancelReadCloser{ReadCloser: response.Body, cancel: cancel}, nil
}

// GenAiCompatResponsesStream 实现 Client:以流式直通 OCI `/actions/v1/responses`,
// 建立成功(2xx)返回 SSE body(调用方负责 Close);建立失败返回 SDK ServiceError,
// 与既有渠道切换/熔断错误分类兼容。请求体须由调用方置 stream:true。
// 总超时置 0(SSE 读 body 不能有总时限);wait 以定时取消模拟等待响应头预算,
// 响应头到达即解除,此后流的生命周期完全由 ctx(下游客户端断开)决定。
func (c *RealClient) GenAiCompatResponsesStream(ctx context.Context, cred Credentials, region string, body []byte, wait time.Duration) (io.ReadCloser, error) {
	ic, err := c.genAiInferenceClient(cred, region)
	if err != nil {
		return nil, err
	}
	client := ic.BaseClient
	common.UpdateEndpointTemplateForOptions(&client)
	common.SetMissingTemplateParams(&client)
	client.HTTPClient = dispatcherWithTimeout(client.HTTPClient, 0)
	request, err := newCompatResponsesRequest(ctx, cred, body)
	if err != nil {
		return nil, err
	}
	return callWithHeaderBudget(ctx, client, request, wait)
}
