package pipeline

import (
	"bytes"
	"encoding/json"
	"fmt"
	"reflect"
	"sort"
	"strings"

	"github.com/santhosh-tekuri/jsonschema/v6"

	"gitea.maximumdirect.net/eric/notarius/internal/framework/contracts"
)

type ArtifactCodecSpec struct {
	Kind         contracts.ArtifactKind
	Schema       contracts.ArtifactSchema
	SchemaDigest string
	MediaType    string
}

// ArtifactCodecTypeError reports a value that does not have the exact Go type
// registered for an artifact kind.
type ArtifactCodecTypeError struct {
	Operation    string
	Kind         contracts.ArtifactKind
	ExpectedType string
	ActualType   string
}

func (e *ArtifactCodecTypeError) Error() string {
	return fmt.Sprintf("%s artifact %q: expected exact Go type %s, got %s", e.Operation, e.Kind, e.ExpectedType, e.ActualType)
}

// ArtifactCodecCompatibilityError reports serialized metadata that does not
// identify the registered representation for an artifact kind.
type ArtifactCodecCompatibilityError struct {
	Kind   contracts.ArtifactKind
	Reason string
}

func (e *ArtifactCodecCompatibilityError) Error() string {
	return fmt.Sprintf("decode artifact %q: %s", e.Kind, e.Reason)
}

// ArtifactCodecOperationError preserves an encode or decode failure from the
// registered domain codec.
type ArtifactCodecOperationError struct {
	Operation string
	Kind      contracts.ArtifactKind
	Err       error
}

func (e *ArtifactCodecOperationError) Error() string {
	return fmt.Sprintf("%s artifact %q: %v", e.Operation, e.Kind, e.Err)
}

func (e *ArtifactCodecOperationError) Unwrap() error { return e.Err }

type ArtifactCodecRegistry struct {
	entries map[contracts.ArtifactKind]artifactCodecEntry
}

type artifactCodecEntry struct {
	spec            ArtifactCodecSpec
	valueType       reflect.Type
	encode          func(any) ([]byte, error)
	encodeCandidate func(any) ([]byte, error)
	metadata        func(any) (map[string]any, error)
	decode          func([]byte) (any, error)
	decodeCandidate func([]byte) (any, error)
}

func NewArtifactCodecRegistry() *ArtifactCodecRegistry {
	return &ArtifactCodecRegistry{entries: make(map[contracts.ArtifactKind]artifactCodecEntry)}
}

// RegisterArtifactCodec registers one codec for T. The concrete type is kept
// private and checked at every erased encode boundary.
func RegisterArtifactCodec[T any](registry *ArtifactCodecRegistry, codec contracts.CandidateArtifactCodec[T]) error {
	if registry == nil {
		return fmt.Errorf("artifact codec registry must not be nil")
	}
	if nilInterface(codec) {
		return fmt.Errorf("artifact codec must not be nil")
	}

	spec, err := artifactCodecSpec(codec.Kind(), codec.Schema(), codec.MediaType())
	if err != nil {
		return err
	}
	if _, ok := registry.entries[spec.Kind]; ok {
		return fmt.Errorf("artifact codec %q is already registered", spec.Kind)
	}

	valueType := reflect.TypeFor[T]()
	entry := artifactCodecEntry{
		spec:      cloneArtifactCodecSpec(spec),
		valueType: valueType,
		encode: func(value any) ([]byte, error) {
			actualType := reflect.TypeOf(value)
			if actualType != valueType {
				return nil, newArtifactCodecTypeError("encode", spec.Kind, valueType, actualType)
			}
			typed, ok := value.(T)
			if !ok {
				return nil, newArtifactCodecTypeError("encode", spec.Kind, valueType, actualType)
			}
			encoded, err := codec.Encode(typed)
			if err != nil {
				return nil, &ArtifactCodecOperationError{Operation: "encode", Kind: spec.Kind, Err: err}
			}
			return append([]byte(nil), encoded...), nil
		},
		decode: func(content []byte) (any, error) {
			decoded, err := codec.Decode(append([]byte(nil), content...))
			if err != nil {
				return nil, &ArtifactCodecOperationError{Operation: "decode", Kind: spec.Kind, Err: err}
			}
			return decoded, nil
		},
		decodeCandidate: func(content []byte) (any, error) {
			decoded, err := codec.DecodeCandidate(append([]byte(nil), content...))
			if err != nil {
				return nil, &ArtifactCodecOperationError{Operation: "decode candidate", Kind: spec.Kind, Err: err}
			}
			return decoded, nil
		},
	}
	entry.encodeCandidate = func(value any) ([]byte, error) {
		typed, err := exactTypedValue[T]("encode candidate artifact", value)
		if err != nil {
			return nil, err
		}
		content, err := codec.EncodeCandidate(typed)
		if err != nil {
			return nil, &ArtifactCodecOperationError{Operation: "encode candidate", Kind: spec.Kind, Err: err}
		}
		return append([]byte(nil), content...), nil
	}
	if provider, ok := any(codec).(interface{ Metadata(T) map[string]any }); ok {
		entry.metadata = func(value any) (map[string]any, error) {
			typed, err := exactTypedValue[T]("artifact metadata", value)
			if err != nil {
				return nil, err
			}
			return cloneMetadata(provider.Metadata(typed))
		}
	}
	if registry.entries == nil {
		registry.entries = make(map[contracts.ArtifactKind]artifactCodecEntry)
	}
	registry.entries[spec.Kind] = entry
	return nil
}

func (r *ArtifactCodecRegistry) Spec(kind contracts.ArtifactKind) (ArtifactCodecSpec, bool) {
	if r == nil {
		return ArtifactCodecSpec{}, false
	}
	entry, ok := r.entries[normalizeArtifactKind(kind)]
	if !ok {
		return ArtifactCodecSpec{}, false
	}
	return cloneArtifactCodecSpec(entry.spec), true
}

func (r *ArtifactCodecRegistry) valueType(kind contracts.ArtifactKind) (reflect.Type, bool) {
	if r == nil {
		return nil, false
	}
	entry, ok := r.entries[normalizeArtifactKind(kind)]
	if !ok {
		return nil, false
	}
	return entry.valueType, true
}

func (r *ArtifactCodecRegistry) RegisteredKinds() []contracts.ArtifactKind {
	if r == nil || len(r.entries) == 0 {
		return nil
	}
	kinds := make([]contracts.ArtifactKind, 0, len(r.entries))
	for kind := range r.entries {
		kinds = append(kinds, kind)
	}
	sort.Slice(kinds, func(i, j int) bool { return kinds[i] < kinds[j] })
	return kinds
}

// Encode serializes an erased framework value after proving its exact
// registered Go type.
func (r *ArtifactCodecRegistry) Encode(kind contracts.ArtifactKind, value any) (contracts.SerializedArtifact, error) {
	entry, normalizedKind, err := r.entry(kind)
	if err != nil {
		return contracts.SerializedArtifact{}, err
	}
	content, err := entry.encode(value)
	if err != nil {
		return contracts.SerializedArtifact{}, err
	}
	return contracts.SerializedArtifact{
		Kind:      normalizedKind,
		Schema:    contracts.CloneArtifactSchema(entry.spec.Schema),
		MediaType: entry.spec.MediaType,
		Content:   content,
	}, nil
}

// Decode verifies serialized identity before invoking the registered codec.
func (r *ArtifactCodecRegistry) Decode(artifact contracts.SerializedArtifact) (any, error) {
	entry, normalizedKind, err := r.entry(artifact.Kind)
	if err != nil {
		return nil, err
	}
	if artifact.Schema.ID != entry.spec.Schema.ID || artifact.Schema.Name != entry.spec.Schema.Name || artifact.Schema.Version != entry.spec.Schema.Version {
		return nil, &ArtifactCodecCompatibilityError{Kind: normalizedKind, Reason: "schema identity does not match registered codec"}
	}
	if contracts.DigestArtifactSchema(artifact.Schema) != entry.spec.SchemaDigest {
		return nil, &ArtifactCodecCompatibilityError{Kind: normalizedKind, Reason: "schema digest does not match registered codec"}
	}
	if strings.TrimSpace(artifact.MediaType) != entry.spec.MediaType {
		return nil, &ArtifactCodecCompatibilityError{Kind: normalizedKind, Reason: "media type does not match registered codec"}
	}
	return entry.decode(artifact.Content)
}

func (r *ArtifactCodecRegistry) entry(kind contracts.ArtifactKind) (artifactCodecEntry, contracts.ArtifactKind, error) {
	if r == nil {
		return artifactCodecEntry{}, "", fmt.Errorf("artifact codec registry must not be nil")
	}
	normalizedKind := normalizeArtifactKind(kind)
	if normalizedKind == "" {
		return artifactCodecEntry{}, "", fmt.Errorf("artifact kind must not be empty")
	}
	entry, ok := r.entries[normalizedKind]
	if !ok {
		return artifactCodecEntry{}, normalizedKind, fmt.Errorf("artifact codec %q is not registered", normalizedKind)
	}
	return entry, normalizedKind, nil
}

func artifactCodecSpec(kind contracts.ArtifactKind, schema contracts.ArtifactSchema, mediaType string) (ArtifactCodecSpec, error) {
	kind = normalizeArtifactKind(kind)
	schema.ID = strings.TrimSpace(schema.ID)
	schema.Name = strings.TrimSpace(schema.Name)
	schema.Version = strings.TrimSpace(schema.Version)
	mediaType = strings.TrimSpace(mediaType)
	switch {
	case kind == "":
		return ArtifactCodecSpec{}, fmt.Errorf("artifact codec kind must not be empty")
	case schema.ID == "":
		return ArtifactCodecSpec{}, fmt.Errorf("artifact codec %q schema id must not be empty", kind)
	case schema.Name == "":
		return ArtifactCodecSpec{}, fmt.Errorf("artifact codec %q schema name must not be empty", kind)
	case schema.Version == "":
		return ArtifactCodecSpec{}, fmt.Errorf("artifact codec %q schema version must not be empty", kind)
	case mediaType == "":
		return ArtifactCodecSpec{}, fmt.Errorf("artifact codec %q media type must not be empty", kind)
	case len(bytes.TrimSpace(schema.JSONSchema)) == 0:
		return ArtifactCodecSpec{}, fmt.Errorf("artifact codec %q JSON Schema must not be empty", kind)
	case !json.Valid(schema.JSONSchema):
		return ArtifactCodecSpec{}, fmt.Errorf("artifact codec %q JSON Schema must be valid JSON", kind)
	}
	schemaDocument, err := jsonschema.UnmarshalJSON(bytes.NewReader(schema.JSONSchema))
	if err != nil {
		return ArtifactCodecSpec{}, fmt.Errorf("artifact codec %q JSON Schema is invalid: %w", kind, err)
	}
	compiler := jsonschema.NewCompiler()
	if err := compiler.AddResource("artifact-schema.json", schemaDocument); err != nil {
		return ArtifactCodecSpec{}, fmt.Errorf("artifact codec %q JSON Schema is invalid: %w", kind, err)
	}
	if _, err := compiler.Compile("artifact-schema.json"); err != nil {
		return ArtifactCodecSpec{}, fmt.Errorf("artifact codec %q JSON Schema is invalid: %w", kind, err)
	}
	schema = contracts.CloneArtifactSchema(schema)
	return ArtifactCodecSpec{
		Kind:         kind,
		Schema:       schema,
		SchemaDigest: contracts.DigestArtifactSchema(schema),
		MediaType:    mediaType,
	}, nil
}

func normalizeArtifactKind(kind contracts.ArtifactKind) contracts.ArtifactKind {
	return contracts.ArtifactKind(strings.TrimSpace(string(kind)))
}

func cloneArtifactCodecSpec(spec ArtifactCodecSpec) ArtifactCodecSpec {
	spec.Schema = contracts.CloneArtifactSchema(spec.Schema)
	return spec
}

func nilInterface(value any) bool {
	if value == nil {
		return true
	}
	reflected := reflect.ValueOf(value)
	switch reflected.Kind() {
	case reflect.Chan, reflect.Func, reflect.Interface, reflect.Map, reflect.Pointer, reflect.Slice:
		return reflected.IsNil()
	default:
		return false
	}
}

func newArtifactCodecTypeError(operation string, kind contracts.ArtifactKind, expected reflect.Type, actual reflect.Type) error {
	expectedName := "<nil>"
	if expected != nil {
		expectedName = expected.String()
	}
	actualName := "<nil>"
	if actual != nil {
		actualName = actual.String()
	}
	return &ArtifactCodecTypeError{
		Operation:    operation,
		Kind:         kind,
		ExpectedType: expectedName,
		ActualType:   actualName,
	}
}
