package openai

import (
	"bytes"
	"encoding/json"
	"errors"
	"fmt"
	"io"
	"net/http"
	"sync"
	"sync/atomic"

	"iop/packages/go/streamgate"
)

const (
	openAIIngressTypedViewName  = "openai.request.semantic"
	openAIRebuiltBodyViewName   = "openai.request.rebuilt"
	openAIUnspecifiedProviderID = "provider.unspecified"
)

var (
	errOpenAIIngressTooLarge = errors.New("openai request body exceeds ingress limit")
	openAIIngressSequence    atomic.Uint64
)

// openAIIngressSnapshot is the request-local owner of the caller's canonical
// JSON body and its bounded semantic ledger. It deliberately contains no HTTP
// headers, provider credentials, logger fields, or cross-request references.
type openAIIngressSnapshot struct {
	mu          sync.RWMutex
	ref         string
	snapshot    *streamgate.IngressSnapshot
	retainedMax int64
	closed      bool
}

// openAIStallRecoveryState is the narrow request-local bridge between the
// private liveness filter and recovery dispatch. It retains only Edge-owned
// provider identity plus the allowlisted probe classification; no provider
// error text, request body, or arbitrary failure metadata enters this state.
type openAIStallRecoveryState struct {
	mu                sync.Mutex
	attemptID         string
	providerID        string
	health            string
	confirmedForClose bool
	recoveryClaimed   bool
}

func (s *openAIStallRecoveryState) claimRecovery() bool {
	if s == nil {
		return false
	}
	s.mu.Lock()
	defer s.mu.Unlock()
	if s.recoveryClaimed {
		return false
	}
	s.recoveryClaimed = true
	return true
}

func (s *openAIStallRecoveryState) arm(attemptID, providerID, health string) {
	if s == nil || attemptID == "" || providerID == "" || providerID == openAIUnspecifiedProviderID {
		return
	}
	s.mu.Lock()
	s.attemptID = attemptID
	s.providerID = providerID
	s.health = health
	s.confirmedForClose = false
	s.mu.Unlock()
}

func (s *openAIStallRecoveryState) claimConfirmedClose(attemptID string) bool {
	if s == nil {
		return false
	}
	s.mu.Lock()
	defer s.mu.Unlock()
	if s.attemptID != attemptID || s.providerID == "" || s.confirmedForClose {
		return false
	}
	s.confirmedForClose = true
	return true
}

func (s *openAIStallRecoveryState) consumeAdmission() (providerID string, allowFallback bool, ok bool) {
	if s == nil {
		return "", false, false
	}
	s.mu.Lock()
	defer s.mu.Unlock()
	if !s.confirmedForClose || s.providerID == "" {
		return "", false, false
	}
	// Health-probe availability proves only that the endpoint is reachable. It
	// never makes the exact request that just stalled safe to replay on the same
	// provider.
	providerID, allowFallback = s.providerID, false
	s.attemptID, s.providerID, s.health = "", "", ""
	s.confirmedForClose = false
	return providerID, allowFallback, true
}

// readOpenAIIngressBody installs the HTTP body limit before reading. The
// standard library reader performs a limit+1 probe internally, so an exact
// limit body succeeds and the first excess byte is reported as overflow.
func readOpenAIIngressBody(w http.ResponseWriter, r *http.Request, maxBytes int64) ([]byte, error) {
	if maxBytes <= 0 {
		return nil, streamgate.ErrIngressSnapshotZeroMaxBytes
	}
	r.Body = http.MaxBytesReader(w, r.Body, maxBytes)
	body, err := io.ReadAll(r.Body)
	if err != nil {
		var maxErr *http.MaxBytesError
		if errors.As(err, &maxErr) {
			return nil, errOpenAIIngressTooLarge
		}
		return nil, err
	}
	return body, nil
}

// buildOpenAIIngressSnapshot transfers the bounded canonical request and a
// serialized semantic view into the shared Core ledger. Equal payloads share
// one backing handle and are counted once.
func buildOpenAIIngressSnapshot(maxBytes int64, canonical []byte, semanticView any) (*openAIIngressSnapshot, error) {
	var semantic []byte
	if raw, ok := semanticView.(json.RawMessage); ok {
		if !json.Valid(raw) {
			return nil, fmt.Errorf("encode OpenAI ingress semantic view: invalid JSON")
		}
		semantic = raw
	} else {
		var err error
		semantic, err = json.Marshal(semanticView)
		if err != nil {
			return nil, fmt.Errorf("encode OpenAI ingress semantic view: %w", err)
		}
	}

	builder, err := streamgate.NewIngressSnapshotBuilder(maxBytes)
	if err != nil {
		return nil, err
	}
	defer builder.Close()

	canonicalHandle, err := builder.IssueOwnedBackingHandle(canonical)
	if err != nil {
		return nil, err
	}
	if err := builder.SetCanonicalWithHandle(canonicalHandle); err != nil {
		return nil, err
	}

	semanticHandle := canonicalHandle
	if !bytes.Equal(semantic, canonical) {
		semanticHandle, err = builder.IssueOwnedBackingHandle(semantic)
		if err != nil {
			return nil, err
		}
	}
	if err := builder.AddTypedView(openAIIngressTypedViewName, semanticHandle); err != nil {
		return nil, err
	}

	snapshot, err := builder.Build()
	if err != nil {
		return nil, err
	}
	ref := fmt.Sprintf("openai.ingress.%d", openAIIngressSequence.Add(1))
	return &openAIIngressSnapshot{
		ref:         ref,
		snapshot:    snapshot,
		retainedMax: snapshot.Accessor().RetainedBytes(),
	}, nil
}

func (s *openAIIngressSnapshot) canonicalBody() ([]byte, error) {
	if s == nil {
		return nil, streamgate.ErrIngressSnapshotClosed
	}
	s.mu.RLock()
	defer s.mu.RUnlock()
	if s.closed || s.snapshot == nil {
		return nil, streamgate.ErrIngressSnapshotClosed
	}
	return s.snapshot.Accessor().CanonicalAlias()
}

func (s *openAIIngressSnapshot) semanticBody() ([]byte, error) {
	if s == nil {
		return nil, streamgate.ErrIngressSnapshotClosed
	}
	s.mu.RLock()
	defer s.mu.RUnlock()
	if s.closed || s.snapshot == nil {
		return nil, streamgate.ErrIngressSnapshotClosed
	}
	return s.snapshot.Accessor().TypedViewAlias(openAIIngressTypedViewName)
}

func (s *openAIIngressSnapshot) recoveryRef() (streamgate.RecoveryRequestSnapshotRef, error) {
	if s == nil {
		return streamgate.RecoveryRequestSnapshotRef{}, streamgate.ErrIngressSnapshotClosed
	}
	s.mu.RLock()
	defer s.mu.RUnlock()
	if s.closed || s.snapshot == nil {
		return streamgate.RecoveryRequestSnapshotRef{}, streamgate.ErrIngressSnapshotClosed
	}
	accessor := s.snapshot.Accessor()
	retained := accessor.RetainedBytes()
	peak := retained
	if s.retainedMax > peak {
		peak = s.retainedMax
	}
	return streamgate.NewRecoveryRequestSnapshotRef(
		s.ref,
		uint64(retained),
		uint64(peak),
		uint64(accessor.MaxBytes()),
	)
}

func (s *openAIIngressSnapshot) reserveRebuild(expectedBytes int64) (*streamgate.IngressSnapshotRebuildGuard, error) {
	if s == nil {
		return nil, streamgate.ErrIngressSnapshotClosed
	}
	s.mu.Lock()
	defer s.mu.Unlock()
	if s.closed || s.snapshot == nil {
		return nil, streamgate.ErrIngressSnapshotClosed
	}
	guard, err := s.snapshot.ReserveRebuild(expectedBytes)
	if err != nil {
		return nil, err
	}
	peak := s.snapshot.Accessor().RetainedBytes() + s.snapshot.ReservedTempBytes()
	if peak > s.retainedMax {
		s.retainedMax = peak
	}
	return guard, nil
}

func (s *openAIIngressSnapshot) accessor() (streamgate.IngressSnapshotAccessor, error) {
	if s == nil {
		return streamgate.IngressSnapshotAccessor{}, streamgate.ErrIngressSnapshotClosed
	}
	s.mu.RLock()
	defer s.mu.RUnlock()
	if s.closed || s.snapshot == nil {
		return streamgate.IngressSnapshotAccessor{}, streamgate.ErrIngressSnapshotClosed
	}
	return s.snapshot.Accessor(), nil
}

func (s *openAIIngressSnapshot) Close() {
	if s == nil {
		return
	}
	s.mu.Lock()
	defer s.mu.Unlock()
	if s.closed {
		return
	}
	s.closed = true
	if s.snapshot != nil {
		s.snapshot.Close()
		s.snapshot = nil
	}
}

func (s *openAIIngressSnapshot) isClosed() bool {
	if s == nil {
		return true
	}
	s.mu.RLock()
	defer s.mu.RUnlock()
	return s.closed || s.snapshot == nil
}

func (s *Server) maxIngressSnapshotBytes() int64 {
	s.mu.RLock()
	defer s.mu.RUnlock()
	return int64(s.cfg.StreamEvidenceGate.EffectiveMaxIngressSnapshotBytes())
}

func writeOpenAIIngressError(w http.ResponseWriter, err error) {
	if errors.Is(err, errOpenAIIngressTooLarge) ||
		errors.Is(err, streamgate.ErrIngressSnapshotLimitExceeded) ||
		errors.Is(err, streamgate.ErrIngressSnapshotRebuildOverflow) ||
		errors.Is(err, streamgate.ErrRecoverySnapshotLimitExceeded) {
		writeError(w, http.StatusRequestEntityTooLarge, "invalid_request_error", "request body exceeds configured limit")
		return
	}
	writeError(w, http.StatusBadRequest, "invalid_request_error", "failed to read request body")
}
