package automation

import (
	"context"
	"errors"
	"math"
	"net/http"
	"strings"

	"github.com/tidbcloud/AgentTeam/internal/api/meshxapi"
	"github.com/tidbcloud/AgentTeam/internal/config"
)

const (
	runnerJSONControlBodyBytes  = int64(16 * 1024)
	runnerJSONMetadataBodyBytes = int64(64 * 1024)
)

type runnerJSONLimits struct {
	control  int64
	callback int64
	log      int64
	artifact int64
}

type runnerRequest struct {
	Request   *http.Request
	workspace string
	token     string
}

type runnerJSON[T any] struct {
	runnerRequest
	Body T
}

func (r runnerRequest) Context() context.Context {
	if r.Request != nil && r.Request.Context() != nil {
		return r.Request.Context()
	}
	return context.Background()
}

func (r runnerRequest) WorkspaceID() string { return r.workspace }

func (r runnerRequest) RunnerToken() string { return r.token }

func handleRunner(handler func(runnerRequest) (meshxapi.Response, error)) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		request, err := newRunnerRequest(r)
		if err != nil {
			writeAutomationHandlerError(w, err)
			return
		}
		response, err := handler(request)
		if err != nil {
			writeAutomationHandlerError(w, err)
			return
		}
		meshxapi.WriteResponse(w, response)
	})
}

func handleRunnerJSON[T any](maxBodyBytes int64, handler func(runnerJSON[T]) (meshxapi.Response, error)) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		request, err := newRunnerRequest(r)
		if err != nil {
			writeAutomationHandlerError(w, err)
			return
		}
		body, err := bindRunnerJSON[T](w, request.Request, maxBodyBytes)
		if err != nil {
			writeAutomationHandlerError(w, err)
			return
		}
		response, err := handler(runnerJSON[T]{runnerRequest: request, Body: body})
		if err != nil {
			writeAutomationHandlerError(w, err)
			return
		}
		meshxapi.WriteResponse(w, response)
	})
}

func bindRunnerJSON[T any](w http.ResponseWriter, r *http.Request, maxBodyBytes int64) (T, error) {
	body, err := meshxapi.BindJSONLimit[T](w, r, maxBodyBytes)
	if err != nil {
		return body, runnerJSONDecodeError(err)
	}
	return body, nil
}

func runnerJSONDecodeError(err error) error {
	var maxBytesErr *http.MaxBytesError
	if errors.As(err, &maxBytesErr) {
		return meshxapi.NewHTTPError(http.StatusRequestEntityTooLarge, "Automation runner request body exceeds maximum size")
	}
	return meshxapi.NewHTTPError(http.StatusBadRequest, "Invalid JSON body")
}

func newRunnerRequest(r *http.Request) (runnerRequest, error) {
	token := bearerToken(r.Header.Get("Authorization"))
	if token == "" {
		return runnerRequest{}, meshxapi.NewHTTPError(http.StatusUnauthorized, "Automation runner token is required")
	}
	return runnerRequest{Request: r, workspace: strings.TrimSpace(r.Header.Get(meshxapi.HeaderWorkspaceID)), token: token}, nil
}

func bearerToken(header string) string {
	fields := strings.Fields(strings.TrimSpace(header))
	if len(fields) == 2 && strings.EqualFold(fields[0], "Bearer") {
		return fields[1]
	}
	return ""
}

func newRunnerJSONLimits(cfg config.AutomationConfig) runnerJSONLimits {
	maxLogChunkBytes := int64(cfg.MaxLogChunkBytes)
	if maxLogChunkBytes <= 0 {
		maxLogChunkBytes = 64 * 1024
	}
	maxArtifactBytes := cfg.MaxArtifactBytes
	if maxArtifactBytes <= 0 {
		maxArtifactBytes = config.DefaultMaxArtifactBytes
	}
	maxJobInputBytes := int64(cfg.MaxJobInputBytes)
	if maxJobInputBytes <= 0 {
		maxJobInputBytes = 128 * 1024
	}
	artifactMetadataBytes := saturatingAdd(runnerJSONMetadataBodyBytes, maxJobInputBytes)
	return runnerJSONLimits{
		control:  runnerJSONControlBodyBytes,
		callback: saturatingAdd(maxJobInputBytes, runnerJSONMetadataBodyBytes),
		log:      saturatingAdd(saturatingMul(maxLogChunkBytes, 6), runnerJSONMetadataBodyBytes),
		artifact: saturatingAdd(encodedBase64Len(maxArtifactBytes), artifactMetadataBytes),
	}
}

func encodedBase64Len(decodedBytes int64) int64 {
	if decodedBytes < 0 {
		return 0
	}
	if decodedBytes > (math.MaxInt64/4)*3-2 {
		return math.MaxInt64
	}
	return ((decodedBytes + 2) / 3) * 4
}

func saturatingAdd(left int64, right int64) int64 {
	if left > math.MaxInt64-right {
		return math.MaxInt64
	}
	return left + right
}

func saturatingMul(left int64, right int64) int64 {
	if left != 0 && right > math.MaxInt64/left {
		return math.MaxInt64
	}
	return left * right
}
