package autogencase

import (
	"bytes"
	"encoding/json"
	"fmt"
	"io"
	"net/http"
	"net/url"
	"strings"
	"time"

	"github.com/icholy/digest"
	"github.com/tidwall/gjson"

	"github.com/tidbcloud/tidb-management-service/functional-test/cases/openapidedicated/autogencase/model"
)

func buildHttpClientWithDigestAuth(tc *model.CaseRecord, openAPIKey string) (*http.Client, error) {
	enviroment := Environment{}
	if err := json.Unmarshal([]byte(tc.Environment), &enviroment); err != nil {
		return nil, err
	}
	var username, password string
	if enviroment.Auth.PublicKey == Placehodler && enviroment.Auth.PrivateKey == Placehodler {
		username, password, _ = strings.Cut(openAPIKey, ":")
	} else {
		username, password = enviroment.Auth.PublicKey, enviroment.Auth.PrivateKey
	}
	return &http.Client{Transport: &digest.Transport{Username: username, Password: password}}, nil
}

func buildRequest(
	tc *model.CaseRecord,
	placeHolderLookup placeHolderLookup,
	openAPIHost string,
) (*http.Request, error) {
	p := tc.Path
	method := tc.Method
	var requestInfo *Request
	var body io.Reader = http.NoBody
	var bodyBytes []byte
	query := make(url.Values)
	if tc.Request != nil && *tc.Request != "{}" {
		req := new(Request)
		if err := json.Unmarshal([]byte(*tc.Request), req); err != nil {
			return nil, err
		}
		requestInfo = req
	}
	if requestInfo != nil {
		// fill path params with the path_params in the request info
		p = placeHolderLookup.buildPathParams(p, requestInfo.PathParams)
		// fill query params with query_params in the request info
		queryparams := gjson.ParseBytes(requestInfo.QueryParams)
		if err := placeHolderLookup.fillQueryParams(&query, &queryparams); err != nil {
			return nil, fmt.Errorf("case %s faile to parse query params %w", tc.CaseID, err)
		}
		// fill body with body in request info
		if len(requestInfo.Body) > 0 {
			out := make(map[string]any)
			bodydata := gjson.ParseBytes(requestInfo.Body)
			placeHolderLookup.fillBody(out, &bodydata)
			bs, err := json.Marshal(out)
			if err != nil {
				return nil, fmt.Errorf("case %s faile to parse body %w", tc.CaseID, err)
			}
			bodyBytes = bs
			body = ReaderWrapper{bytes.NewReader(bs)}
		}
	}

	u := fmt.Sprintf("https://%s/v1beta1%s", openAPIHost, p)
	if encoded := query.Encode(); encoded != "" {
		u = fmt.Sprintf("%s?%s", u, encoded)
	}
	// build request
	req, err := http.NewRequest(method, u, body)
	if err != nil {
		return nil, err
	}
	if len(bodyBytes) > 0 {
		req.ContentLength = int64(len(bodyBytes))
		req.GetBody = func() (io.ReadCloser, error) {
			return ReaderWrapper{bytes.NewReader(bodyBytes)}, nil
		}
	}
	return req, nil
}

func buildResultRecord(
	tc *model.CaseRecord,
	req *http.Request,
	resp *http.Response,
	dur time.Duration,
) (*model.ResultRecord, error) {
	result := new(model.ResultRecord)
	traceID := resp.Header.Get("x-debug-trace-id")
	service, _, _ := strings.Cut(tc.OperationID, "_")
	result.SwaggerCommitID = tc.SpecCommitID
	result.CaseID = tc.CaseID
	result.Service = service
	result.Operation = tc.OperationID
	result.Path = req.URL.Path
	result.Method = req.Method
	result.ExpectedStatus = tc.ExpectedStatus
	result.ResponseStatus = resp.StatusCode
	result.Expected = tc.ExpectedStatus == resp.StatusCode
	result.Ignored = tc.Ignored
	result.DurationMilliSeconds = dur.Milliseconds()
	result.XDebugTraceID = traceID
	result.GenerationID = tc.GenerationID
	{
		if rseeker, ok := resp.Body.(io.ReadSeeker); ok {
			_, _ = rseeker.Seek(0, 0)
		}
		bodyCpy, err := io.ReadAll(resp.Body)
		if err != nil {
			return nil, err
		}
		result.ResponseBody = string(bodyCpy)
	}
	{
		if rseeker, ok := req.Body.(io.ReadSeeker); ok {
			_, _ = rseeker.Seek(0, 0)
		}
		bodyCpy, err := io.ReadAll(req.Body)
		if err != nil {
			return nil, err
		}
		result.Body = string(bodyCpy)
	}
	return result, nil
}
