package exec

import (
	"context"
	"encoding/json"
	"fmt"
	"mime/multipart"
	"strings"

	"github.com/tidbcloud/AgentTeam/desktop/internal/wire/apiclient"
)

// remoteReadTransport is the concrete remote read boundary. It keeps the
// retry-safe GET implementation and its catalog capability in one transport
// object, rather than allowing the audit table to guess from an action name.
type remoteReadTransport struct{ client *apiclient.Client }

func (transport remoteReadTransport) get(ctx context.Context, path string, result any) error {
	if transport.client == nil {
		return fmt.Errorf("remote read transport client is unavailable")
	}
	return transport.client.Get(ctx, path, result)
}

func (transport remoteReadTransport) post(ctx context.Context, path string, body, result any) error {
	if transport.client == nil {
		return fmt.Errorf("remote read transport client is unavailable")
	}
	return transport.client.Post(ctx, path, body, result)
}

func (remoteReadTransport) capability(_ ...auditPath) builtinInvokerCapability {
	// Client.Get owns one bounded transient retry for every remote read; the
	// capability is derived from this executable transport, never from an
	// action-name policy row.
	return remoteReadCapability()
}

// naturalMutationTransport is the non-retried HTTP mutation boundary. The
// absence of a receipt is explicit in the transport type and capability.
type naturalMutationTransport struct {
	client *apiclient.Client
	// turnTargets is the daemon-local, authorization-fenced context dependency
	// sealed into task.create's executable object. Other natural mutations keep
	// it nil.
	turnTargets TurnTargetReader
}

func (transport naturalMutationTransport) post(ctx context.Context, path string, body, result any) error {
	if transport.client == nil {
		return fmt.Errorf("natural mutation transport client is unavailable")
	}
	return transport.client.Post(ctx, path, body, result)
}

func (transport naturalMutationTransport) put(ctx context.Context, path string, body, result any) error {
	if transport.client == nil {
		return fmt.Errorf("natural mutation transport client is unavailable")
	}
	return transport.client.Put(ctx, path, body, result)
}

func (transport naturalMutationTransport) patch(ctx context.Context, path string, body, result any) error {
	if transport.client == nil {
		return fmt.Errorf("natural mutation transport client is unavailable")
	}
	return transport.client.Patch(ctx, path, body, result)
}

func (transport naturalMutationTransport) delete(ctx context.Context, path string, result any) error {
	if transport.client == nil {
		return fmt.Errorf("natural mutation transport client is unavailable")
	}
	return transport.client.Delete(ctx, path, result)
}

func (transport naturalMutationTransport) multipartRaw(ctx context.Context, path string, body []byte, contentType string, result any) error {
	if transport.client == nil {
		return fmt.Errorf("natural mutation transport client is unavailable")
	}
	return transport.client.PostMultipartRaw(ctx, path, body, contentType, result)
}

func (transport naturalMutationTransport) multipartStream(ctx context.Context, path string, write func(*multipart.Writer) error, result any) error {
	if transport.client == nil {
		return fmt.Errorf("natural mutation transport client is unavailable")
	}
	return transport.client.PostMultipartStream(ctx, path, write, result)
}

func (naturalMutationTransport) capability(_ ...auditPath) builtinInvokerCapability {
	return naturalMutationCapability()
}

// requestReceiptTransport is the concrete receipt-backed client boundary used
// by Agent, Team, Channel, Role, and Skill approval routes. Its capability is
// declared next to the method that actually invokes PostIdempotentWithHeaders;
// route registration consumes this attested boundary rather than a policy row.
type requestReceiptTransport struct{ client *apiclient.Client }

func (transport requestReceiptTransport) post(ctx context.Context, path string, body, result any, headers map[string]string) error {
	if transport.client == nil {
		return fmt.Errorf("receipt transport client is unavailable")
	}
	return transport.client.PostIdempotentWithHeaders(ctx, path, body, result, headers)
}

func (transport requestReceiptTransport) capability(paths ...auditPath) builtinInvokerCapability {
	if len(paths) == 0 {
		paths = []auditPath{inputPath("requestId")}
	}
	return requestReceiptCapability(paths...)
}

// idempotencyReceiptTransport is the concrete schedule write boundary. Both
// POST create and PATCH update use the same durable idempotency receipt.
type idempotencyReceiptTransport struct{ client *apiclient.Client }

func (transport idempotencyReceiptTransport) post(ctx context.Context, path string, body, result any, headers map[string]string) error {
	if transport.client == nil {
		return fmt.Errorf("idempotency receipt transport client is unavailable")
	}
	return transport.client.PostIdempotentWithHeaders(ctx, path, body, result, headers)
}

func (transport idempotencyReceiptTransport) patch(ctx context.Context, path string, body, result any, headers map[string]string) error {
	if transport.client == nil {
		return fmt.Errorf("idempotency receipt transport client is unavailable")
	}
	return transport.client.PatchIdempotentWithHeaders(ctx, path, body, result, headers)
}

func (transport idempotencyReceiptTransport) capability(paths ...auditPath) builtinInvokerCapability {
	if len(paths) == 0 {
		paths = []auditPath{inputPath("idempotencyKey")}
	}
	return idempotencyCapability(paths...)
}

// workflowReceiptTransport is the operation-specific Workflow boundary. Read
// operations intentionally use the ordinary Post path; mutation operations use
// this receipt method with the server-generated command selector.
type workflowReceiptTransport struct {
	client        *apiclient.Client
	selectorPaths []auditPath
}

func (transport workflowReceiptTransport) post(ctx context.Context, path string, body, result any, headers map[string]string) error {
	if transport.client == nil {
		return fmt.Errorf("Workflow receipt transport client is unavailable")
	}
	return transport.client.PostIdempotentWithHeaders(ctx, path, body, result, headers)
}

func (transport workflowReceiptTransport) postOnce(ctx context.Context, path string, body, result any, headers map[string]string) error {
	if transport.client == nil {
		return fmt.Errorf("Workflow receipt transport client is unavailable")
	}
	return transport.client.PostWithHeaders(ctx, path, body, result, headers)
}

func (transport workflowReceiptTransport) capability(paths ...auditPath) builtinInvokerCapability {
	if len(paths) == 0 {
		paths = transport.selectorPaths
	}
	if len(paths) == 0 {
		paths = []auditPath{inputPath("action")}
	}
	return workflowCapability(paths...)
}

// replayHeaders returns the stable transport selectors used by server mutation
// receipts. The body remains authoritative; headers make the selector visible
// to the API boundary and let receipt-backed clients retry the exact request.
func replayHeaders(key string) map[string]string {
	key = strings.TrimSpace(key)
	if key == "" {
		return nil
	}
	return map[string]string{
		"Idempotency-Key": key,
		"X-Request-ID":    key,
	}
}

func requestIDFromPayload(input any) string {
	raw, err := json.Marshal(input)
	if err != nil {
		return ""
	}
	var fields map[string]json.RawMessage
	if err := json.Unmarshal(raw, &fields); err != nil {
		return ""
	}
	for _, key := range []string{"idempotencyKey", "requestId"} {
		var value string
		if err := json.Unmarshal(fields[key], &value); err == nil && strings.TrimSpace(value) != "" {
			return strings.TrimSpace(value)
		}
	}
	return ""
}

const maxReplaySelectorBytes = 128

func validateReplaySelector(value string) error {
	value = strings.TrimSpace(value)
	if value == "" || len(value) > maxReplaySelectorBytes || strings.ContainsAny(value, "\r\n") {
		return fmt.Errorf("replay selector must be 1-%d bytes without control characters", maxReplaySelectorBytes)
	}
	return nil
}

func validateReplayHeaders(domain, action string, input json.RawMessage, idempotencyHeader, requestHeader string) error {
	idempotencyHeader = strings.TrimSpace(idempotencyHeader)
	requestHeader = strings.TrimSpace(requestHeader)
	for _, item := range []struct{ name, value string }{{"Idempotency-Key", idempotencyHeader}, {"X-Request-ID", requestHeader}} {
		if item.value != "" {
			if err := validateReplaySelector(item.value); err != nil {
				return NewError(CodeValidation, item.name+": "+err.Error())
			}
		}
	}
	if idempotencyHeader != "" && requestHeader != "" && idempotencyHeader != requestHeader {
		return NewError(CodeConflict, fmt.Sprintf("%s %s replay selectors disagree", domain, action))
	}
	var fields map[string]json.RawMessage
	if err := json.Unmarshal(input, &fields); err == nil {
		var bodyIdempotency, bodyRequest string
		_ = json.Unmarshal(fields["idempotencyKey"], &bodyIdempotency)
		_ = json.Unmarshal(fields["requestId"], &bodyRequest)
		bodyIdempotency, bodyRequest = strings.TrimSpace(bodyIdempotency), strings.TrimSpace(bodyRequest)
		for _, item := range []struct{ name, value string }{{"idempotencyKey", bodyIdempotency}, {"requestId", bodyRequest}} {
			if item.value != "" {
				if err := validateReplaySelector(item.value); err != nil {
					return NewError(CodeValidation, item.name+": "+err.Error())
				}
			}
		}
		if bodyIdempotency != "" && bodyRequest != "" && bodyIdempotency != bodyRequest {
			return NewError(CodeConflict, fmt.Sprintf("%s %s body replay selectors disagree", domain, action))
		}
	}
	if idempotencyHeader == "" && requestHeader == "" {
		return nil
	}
	bodyKey := requestIDFromPayload(input)
	headerKey := idempotencyHeader
	if headerKey == "" {
		headerKey = requestHeader
	}
	if bodyKey == "" || bodyKey != headerKey {
		return NewError(CodeConflict, fmt.Sprintf("%s %s replay selector does not match the request", domain, action))
	}
	return nil
}
