package dialect

import (
	"context"
	"errors"
	"fmt"
	"io"
	"sort"
	"strings"

	"github.com/pingcap-inc/nexuscrm-api-host/hostruntime"
)

type InternalConditioner interface {
	error
	InternalCondition() bool
}

func IsInternal(err error) bool {
	var c InternalConditioner
	return errors.As(err, &c) && c.InternalCondition()
}

// Dialect is the transport-neutral API dialect shape consumed by api-host.
// Implementations declare route data and handlers; dialecttransport owns gin.
type Dialect interface {
	Name() string
	OwningPack() string
	BasePath() string
	Versions() []string
	Manifest() []Route
	ErrorProjector() Projector
	InternalErrorProjector() InternalErrorProjector
}

type Permission string

type GateMode int

const (
	GateNone GateMode = iota
	GateAll
	GateAny
)

type Gate struct {
	Mode        GateMode
	Permissions []Permission
}

type Route struct {
	Method         string
	Path           string
	Gate           Gate
	BodyMode       RequestBodyMode
	BodyLimitBytes int64
	Handler        Handler
}

type RequestBodyMode int

const (
	BodyBuffered RequestBodyMode = iota
	BodyStream
)

type Handler func(ctx context.Context, req *Request, deps Deps) (*Response, error)

type Request struct {
	Version            string
	User               *hostruntime.UserSession
	Params             map[string]string
	Query              map[string][]string
	Header             map[string][]string
	Body               []byte
	BodyStream         io.Reader
	ContentLength      int64
	RateLimitSubjectID string
}

type BodyStreamErrorKind string

const (
	BodyStreamTooLarge   BodyStreamErrorKind = "BodyStreamTooLarge"
	BodyStreamReadFailed BodyStreamErrorKind = "BodyStreamReadFailed"
)

type BodyStreamError struct {
	Kind  BodyStreamErrorKind
	Cause error
}

func (e *BodyStreamError) Error() string {
	switch e.Kind {
	case BodyStreamTooLarge:
		return "request body exceeds configured stream limit"
	case BodyStreamReadFailed:
		return "request body stream read failed"
	default:
		return "request body stream error"
	}
}

func (e *BodyStreamError) Unwrap() error { return e.Cause }

func IsBodyStreamTooLarge(err error) bool {
	var streamErr *BodyStreamError
	return errors.As(err, &streamErr) && streamErr.Kind == BodyStreamTooLarge
}

func IsBodyStreamReadFailed(err error) bool {
	var streamErr *BodyStreamError
	return errors.As(err, &streamErr) && streamErr.Kind == BodyStreamReadFailed
}

type Response struct {
	Status  int
	Body    any
	Headers map[string]string
	Stream  *ResponseStream
}

type ResponseStream struct {
	Reader        io.ReadCloser
	ContentType   string
	ContentLength *int64
}

type Mount struct {
	Dialect  string
	BasePath string
	Version  string
	Path     string
}

type DispatchRequest struct {
	Method string
	Path   string
	Header map[string]string
	Body   []byte
}

type DispatchResponse struct {
	Status int
	Header map[string]string
	Body   []byte
}

type Dispatcher func(ctx context.Context, req DispatchRequest) (*DispatchResponse, error)

// Deps carries host-supplied capabilities without making api-host depend on a
// concrete kernel/contract repo. The backend adapter is responsible for
// projecting Contracts into the legacy dialect contract it invokes.
type Deps struct {
	Contracts any
	Dispatch  Dispatcher
}

type Projector func(req *Request, err *DomainWireError) (status int, body any, projected bool)

type InternalErrorProjector func(req *Request) (status int, body any)

type WireConditionKind string

const (
	WireInvalidReference WireConditionKind = "InvalidReference"
	WireFieldIntegrity   WireConditionKind = "FieldIntegrity"
	WireInvalidOperation WireConditionKind = "InvalidOperation"
	WireInvalidField     WireConditionKind = "InvalidField"
	WireNotFound         WireConditionKind = "NotFound"
	WireMalformedRequest WireConditionKind = "MalformedRequest"
	WireRequestTooLarge  WireConditionKind = "RequestTooLarge"
)

type CreateFromSourceGuardDetails struct {
	ObjectName  string
	ActionNames []string
}

func (d *CreateFromSourceGuardDetails) Clone() *CreateFromSourceGuardDetails {
	if d == nil {
		return nil
	}
	return &CreateFromSourceGuardDetails{
		ObjectName:  d.ObjectName,
		ActionNames: append([]string(nil), d.ActionNames...),
	}
}

type DomainWireError struct {
	Kind                  WireConditionKind
	Message               string
	Fields                []string
	Cause                 error
	CreateFromSourceGuard *CreateFromSourceGuardDetails
}

func (e *DomainWireError) Error() string {
	if e == nil {
		return ""
	}
	if e.Cause != nil {
		return fmt.Sprintf("%s: %v", e.Message, e.Cause)
	}
	return e.Message
}

func (e *DomainWireError) Unwrap() error {
	if e == nil {
		return nil
	}
	return e.Cause
}

func (e *DomainWireError) WithCause(err error) *DomainWireError {
	e.Cause = err
	return e
}

func NewInvalidReferenceError(message string, fields ...string) *DomainWireError {
	return &DomainWireError{Kind: WireInvalidReference, Message: message, Fields: fields}
}

func NewFieldIntegrityError(message string, fields ...string) *DomainWireError {
	return &DomainWireError{Kind: WireFieldIntegrity, Message: message, Fields: fields}
}

func NewInvalidOperationError(message string) *DomainWireError {
	return &DomainWireError{Kind: WireInvalidOperation, Message: message}
}

func NewCreateFromSourceGuardError(objectName string, actionNames []string) *DomainWireError {
	details := &CreateFromSourceGuardDetails{
		ObjectName:  objectName,
		ActionNames: dedupAndSortStrings(actionNames),
	}
	return &DomainWireError{
		Kind:                  WireInvalidOperation,
		Message:               FormatCreateFromSourceGuardMessage(objectName, details.ActionNames, "the owning standard action", "one of the owning standard actions"),
		CreateFromSourceGuard: details,
	}
}

func FormatCreateFromSourceGuardMessage(objectName string, actionReferences []string, singularTarget, pluralTarget string) string {
	actionReferences = dedupAndSortStrings(actionReferences)
	if len(actionReferences) == 1 {
		return fmt.Sprintf(
			"Direct create of %s with a configured source-FK populated is not permitted. Invoke %s instead: %s",
			objectName, singularTarget, actionReferences[0],
		)
	}
	return fmt.Sprintf(
		"Direct create of %s with a configured source-FK populated is not permitted. Invoke %s instead: %s",
		objectName, pluralTarget, strings.Join(actionReferences, ", "),
	)
}

func NewInvalidFieldError(message string, fields ...string) *DomainWireError {
	return &DomainWireError{Kind: WireInvalidField, Message: message, Fields: fields}
}

func NewNotFoundWireError(message string) *DomainWireError {
	return &DomainWireError{Kind: WireNotFound, Message: message}
}

func NewMalformedRequestError(message string) *DomainWireError {
	return &DomainWireError{Kind: WireMalformedRequest, Message: message}
}

func NewRequestTooLargeError(message string) *DomainWireError {
	return &DomainWireError{Kind: WireRequestTooLarge, Message: message}
}

func dedupAndSortStrings(in []string) []string {
	if len(in) == 0 {
		return nil
	}
	seen := make(map[string]struct{}, len(in))
	out := make([]string, 0, len(in))
	for _, s := range in {
		if _, ok := seen[s]; ok {
			continue
		}
		seen[s] = struct{}{}
		out = append(out, s)
	}
	sort.Strings(out)
	return out
}
