package oci

import (
	"context"
	"errors"
	"io"
	"net/http"
	"strings"
	"testing"
	"time"

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

// staticDispatcher 是非 *http.Client 的自定义 dispatcher,用于降级分支。
type staticDispatcher struct{}

func (staticDispatcher) Do(*http.Request) (*http.Response, error) { return nil, nil }

func TestDispatcherWithTimeout(t *testing.T) {
	tr := &http.Transport{}
	tests := []struct {
		name    string
		in      common.HTTPRequestDispatcher
		timeout time.Duration
		check   func(t *testing.T, out common.HTTPRequestDispatcher)
	}{
		{
			name: "http.Client 换总超时并保留 Transport", in: &http.Client{Transport: tr, Timeout: 60 * time.Second},
			timeout: 300 * time.Second,
			check: func(t *testing.T, out common.HTTPRequestDispatcher) {
				hc, ok := out.(*http.Client)
				if !ok || hc.Timeout != 300*time.Second || hc.Transport != tr {
					t.Fatalf("期望拷贝 client 且 Timeout=300s、Transport 保留, 得到 %#v", out)
				}
			},
		},
		{
			name: "timeout=0 表示无总超时", in: &http.Client{Timeout: 60 * time.Second}, timeout: 0,
			check: func(t *testing.T, out common.HTTPRequestDispatcher) {
				if hc := out.(*http.Client); hc.Timeout != 0 {
					t.Fatalf("期望 Timeout=0, 得到 %v", hc.Timeout)
				}
			},
		},
		{
			name: "非 http.Client 原样返回", in: staticDispatcher{}, timeout: 300 * time.Second,
			check: func(t *testing.T, out common.HTTPRequestDispatcher) {
				if _, ok := out.(staticDispatcher); !ok {
					t.Fatalf("期望原样返回自定义 dispatcher, 得到 %#v", out)
				}
			},
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) { tt.check(t, dispatcherWithTimeout(tt.in, tt.timeout)) })
	}
}

// ctxReader 模拟真实 HTTP body:请求 ctx 取消后读即失败。
type ctxReader struct {
	ctx context.Context
	r   io.Reader
}

func (c *ctxReader) Read(p []byte) (int, error) {
	if err := c.ctx.Err(); err != nil {
		return 0, err
	}
	return c.r.Read(p)
}

// fakeCaller 在 delay 后返回响应;期间 ctx 取消则按真实行为返回 ctx 错误。
type fakeCaller struct {
	delay   time.Duration
	body    string
	gotCtx  context.Context
	failErr error
}

func (f *fakeCaller) Call(ctx context.Context, _ *http.Request) (*http.Response, error) {
	f.gotCtx = ctx
	if f.failErr != nil {
		return nil, f.failErr
	}
	select {
	case <-time.After(f.delay):
		body := &ctxReader{ctx: ctx, r: strings.NewReader(f.body)}
		return &http.Response{StatusCode: http.StatusOK, Body: io.NopCloser(body)}, nil
	case <-ctx.Done():
		return nil, ctx.Err()
	}
}

func TestCallWithHeaderBudget(t *testing.T) {
	req, _ := http.NewRequest(http.MethodPost, "/actions/v1/responses", nil)
	t.Run("预算内返回响应头后长读不受预算影响", func(t *testing.T) {
		fc := &fakeCaller{delay: 0, body: "data: hello"}
		stream, err := callWithHeaderBudget(context.Background(), fc, req, 30*time.Millisecond)
		if err != nil {
			t.Fatalf("callWithHeaderBudget = %v", err)
		}
		defer stream.Close()
		time.Sleep(90 * time.Millisecond) // 远超预算,验证响应头到达后预算已解除
		payload, err := io.ReadAll(stream)
		if err != nil || string(payload) != "data: hello" {
			t.Fatalf("预算解除后读流 = %q, %v; 期望完整 body", payload, err)
		}
	})
	t.Run("超预算未返回响应头即取消", func(t *testing.T) {
		fc := &fakeCaller{delay: time.Minute}
		start := time.Now()
		_, err := callWithHeaderBudget(context.Background(), fc, req, 30*time.Millisecond)
		if !errors.Is(err, context.Canceled) {
			t.Fatalf("期望 context.Canceled, 得到 %v", err)
		}
		if elapsed := time.Since(start); elapsed > 5*time.Second {
			t.Fatalf("取消耗时 %v, 未受预算约束", elapsed)
		}
	})
	t.Run("关闭流时取消派生 ctx", func(t *testing.T) {
		fc := &fakeCaller{delay: 0, body: "x"}
		stream, err := callWithHeaderBudget(context.Background(), fc, req, time.Minute)
		if err != nil {
			t.Fatalf("callWithHeaderBudget = %v", err)
		}
		stream.Close()
		if fc.gotCtx.Err() == nil {
			t.Fatal("Close 后派生 ctx 应已取消")
		}
	})
	t.Run("建立失败时同样取消派生 ctx", func(t *testing.T) {
		fc := &fakeCaller{failErr: errors.New("boom")}
		if _, err := callWithHeaderBudget(context.Background(), fc, req, time.Minute); err == nil {
			t.Fatal("期望建立失败")
		}
		if fc.gotCtx.Err() == nil {
			t.Fatal("失败路径派生 ctx 应已取消")
		}
	})
}
