package openai

import (
	"bytes"
	"context"
	"crypto/sha256"
	"encoding/binary"
	"encoding/json"
	"errors"
	"sync"

	edgeservice "iop/apps/edge/internal/service"
)

var errSingleRequestClosedTerminal = errors.New("single-request stage reached a closed terminal")

// singleRequestTerminalFailure transports only one validated closed
// disposition. The cause is always a package sentinel used for errors.Is; raw
// provider/tool errors are deliberately not retained.
type singleRequestTerminalFailure struct {
	disposition edgeservice.SingleRequestTerminalDisposition
	cause       error
}

func (e *singleRequestTerminalFailure) Error() string { return errSingleRequestClosedTerminal.Error() }
func (e *singleRequestTerminalFailure) Unwrap() error { return e.cause }

func singleRequestTerminalDisposition(err error) (edgeservice.SingleRequestTerminalDisposition, bool) {
	var terminal *singleRequestTerminalFailure
	if !errors.As(err, &terminal) || terminal == nil || terminal.disposition.Validate() != nil {
		return edgeservice.SingleRequestTerminalDisposition{}, false
	}
	return terminal.disposition, true
}

type singleRequestQualityGate struct {
	mu         sync.Mutex
	toolCycles map[string]map[[sha256.Size]byte]struct{}
}

func newSingleRequestQualityGate() *singleRequestQualityGate {
	return &singleRequestQualityGate{toolCycles: make(map[string]map[[sha256.Size]byte]struct{})}
}

func singleRequestQualityGateOrNew(gate *singleRequestQualityGate) *singleRequestQualityGate {
	if gate != nil {
		return gate
	}
	return newSingleRequestQualityGate()
}

func (g *singleRequestQualityGate) failure(kind edgeservice.SingleRequestTerminalKind, class edgeservice.SingleRequestTerminalErrorClass, cause error) error {
	disposition := edgeservice.SingleRequestTerminalDisposition{Kind: kind, ErrorClass: class}
	if disposition.Validate() != nil {
		disposition = edgeservice.SingleRequestTerminalDisposition{Kind: edgeservice.SingleRequestTerminalError, ErrorClass: edgeservice.SingleRequestTerminalErrorProvider}
	}
	return &singleRequestTerminalFailure{disposition: disposition, cause: cause}
}

func (g *singleRequestQualityGate) reclassify(err, cause error) error {
	if disposition, ok := singleRequestTerminalDisposition(err); ok {
		return g.failure(disposition.Kind, disposition.ErrorClass, cause)
	}
	return g.providerFailure(context.Background(), err, cause)
}

func (g *singleRequestQualityGate) providerFailure(ctx context.Context, err, cause error) error {
	if disposition, ok := singleRequestTerminalDisposition(err); ok {
		return g.failure(disposition.Kind, disposition.ErrorClass, cause)
	}
	switch {
	case ctx != nil && errors.Is(ctx.Err(), context.Canceled):
		return g.failure(edgeservice.SingleRequestTerminalCancelled, "", cause)
	case ctx != nil && errors.Is(ctx.Err(), context.DeadlineExceeded), errors.Is(err, context.DeadlineExceeded):
		return g.failure(edgeservice.SingleRequestTerminalError, edgeservice.SingleRequestTerminalErrorTimeout, cause)
	case errors.Is(err, errProviderStageOutputLimit):
		return g.failure(edgeservice.SingleRequestTerminalLength, "", cause)
	case errors.Is(err, errProviderStageContextLimit):
		return g.failure(edgeservice.SingleRequestTerminalError, edgeservice.SingleRequestTerminalErrorContext, cause)
	case errors.Is(err, errProviderStageMalformed):
		return g.failure(edgeservice.SingleRequestTerminalError, edgeservice.SingleRequestTerminalErrorMalformed, cause)
	default:
		return g.failure(edgeservice.SingleRequestTerminalError, edgeservice.SingleRequestTerminalErrorProvider, cause)
	}
}

func (g *singleRequestQualityGate) validation(cause error) error {
	return g.failure(edgeservice.SingleRequestTerminalError, edgeservice.SingleRequestTerminalErrorValidation, cause)
}

func (g *singleRequestQualityGate) malformed(cause error) error {
	return g.failure(edgeservice.SingleRequestTerminalError, edgeservice.SingleRequestTerminalErrorMalformed, cause)
}

func (g *singleRequestQualityGate) budget(cause error) error {
	return g.failure(edgeservice.SingleRequestTerminalError, edgeservice.SingleRequestTerminalErrorBudget, cause)
}

func (g *singleRequestQualityGate) length(cause error) error {
	return g.failure(edgeservice.SingleRequestTerminalLength, "", cause)
}

func (g *singleRequestQualityGate) contextLimit(cause error) error {
	return g.failure(edgeservice.SingleRequestTerminalError, edgeservice.SingleRequestTerminalErrorContext, cause)
}

func (g *singleRequestQualityGate) internalTool(cause error) error {
	return g.failure(edgeservice.SingleRequestTerminalError, edgeservice.SingleRequestTerminalErrorInternalTool, cause)
}

func (g *singleRequestQualityGate) serviceFailure(ctx context.Context, err, cause error) error {
	if disposition, ok := singleRequestTerminalDisposition(err); ok {
		return g.failure(disposition.Kind, disposition.ErrorClass, cause)
	}
	switch {
	case ctx != nil && errors.Is(ctx.Err(), context.Canceled), errors.Is(err, edgeservice.ErrSingleRequestCancelled):
		return g.failure(edgeservice.SingleRequestTerminalCancelled, "", cause)
	case ctx != nil && errors.Is(ctx.Err(), context.DeadlineExceeded), errors.Is(err, context.DeadlineExceeded):
		return g.failure(edgeservice.SingleRequestTerminalError, edgeservice.SingleRequestTerminalErrorTimeout, cause)
	case errors.Is(err, edgeservice.ErrSingleRequestInternalToolBudget):
		return g.budget(cause)
	case errors.Is(err, edgeservice.ErrSingleRequestInternalToolInvalidCall), errors.Is(err, edgeservice.ErrSingleRequestInvalidState), errors.Is(err, edgeservice.ErrSingleRequestInvalidSequence):
		return g.malformed(cause)
	default:
		return g.internalTool(cause)
	}
}

// observeToolCycle hashes one bounded canonical action/result pair and rejects
// the first proven duplicate within the same request stage. Only fixed hashes
// are retained; arguments and tool output never enter guard state.
func (g *singleRequestQualityGate) observeToolCycle(stage, name string, arguments json.RawMessage, result edgeservice.InternalWorkspaceToolResult, cause error) error {
	if g == nil || stage == "" || name == "" || len(arguments) == 0 {
		return singleRequestQualityGateOrNew(g).malformed(cause)
	}
	success := result.Status == "success" && result.ErrorCode == ""
	repairableNotFound := result.Status == "error" && result.ErrorCode == "not_found"
	if !success && !repairableNotFound {
		switch result.Status {
		case "timeout":
			return g.failure(edgeservice.SingleRequestTerminalError, edgeservice.SingleRequestTerminalErrorTimeout, cause)
		case "cancelled":
			return g.failure(edgeservice.SingleRequestTerminalError, edgeservice.SingleRequestTerminalErrorInternalTool, cause)
		case "invalid":
			return g.malformed(cause)
		default:
			if result.ErrorCode == "invalid_request" {
				return g.malformed(cause)
			}
			return g.internalTool(cause)
		}
	}

	canonicalArguments, err := canonicalSingleRequestJSON(arguments)
	if err != nil {
		return g.malformed(cause)
	}
	hash := sha256.New()
	writeSingleRequestFingerprintPart(hash, []byte(name))
	writeSingleRequestFingerprintPart(hash, canonicalArguments)
	writeSingleRequestFingerprintPart(hash, []byte(result.Status))
	writeSingleRequestFingerprintPart(hash, []byte(result.ErrorCode))
	writeSingleRequestFingerprintPart(hash, result.Content)
	for _, entry := range result.Entries {
		writeSingleRequestFingerprintPart(hash, []byte(entry))
	}
	writeSingleRequestFingerprintPart(hash, result.Stdout)
	writeSingleRequestFingerprintPart(hash, result.Stderr)
	var scalar [5]byte
	binary.BigEndian.PutUint32(scalar[:4], uint32(result.ExitCode))
	if result.Truncated {
		scalar[4] = 1
	}
	writeSingleRequestFingerprintPart(hash, scalar[:])
	var fingerprint [sha256.Size]byte
	copy(fingerprint[:], hash.Sum(nil))

	g.mu.Lock()
	defer g.mu.Unlock()
	seen := g.toolCycles[stage]
	if seen == nil {
		seen = make(map[[sha256.Size]byte]struct{})
		g.toolCycles[stage] = seen
	}
	if _, repeated := seen[fingerprint]; repeated {
		return g.failure(edgeservice.SingleRequestTerminalError, edgeservice.SingleRequestTerminalErrorRepetition, cause)
	}
	seen[fingerprint] = struct{}{}
	return nil
}

func canonicalSingleRequestJSON(raw []byte) ([]byte, error) {
	if validateSingleRequestJSON(raw) != nil {
		return nil, errProviderStageMalformed
	}
	decoder := json.NewDecoder(bytes.NewReader(raw))
	decoder.UseNumber()
	var value any
	if err := decoder.Decode(&value); err != nil {
		return nil, errProviderStageMalformed
	}
	canonical, err := json.Marshal(value)
	if err != nil {
		return nil, errProviderStageMalformed
	}
	return canonical, nil
}

type singleRequestFingerprintWriter interface {
	Write([]byte) (int, error)
}

func writeSingleRequestFingerprintPart(hash singleRequestFingerprintWriter, value []byte) {
	var size [8]byte
	binary.BigEndian.PutUint64(size[:], uint64(len(value)))
	_, _ = hash.Write(size[:])
	_, _ = hash.Write(value)
}
