package appauthority

import (
	"bytes"
	"encoding/json"
	"fmt"
	"io"
	"unicode/utf16"
	"unicode/utf8"
)

type appManifestJSON struct {
	SchemaVersion              string                    `json:"schema_version"`
	AppID                      string                    `json:"app_id"`
	PublisherID                string                    `json:"publisher_id"`
	SemanticVersion            string                    `json:"semantic_version"`
	MetadataBundleDigest       string                    `json:"metadata_bundle_digest"`
	Dependencies               []dependencyJSON          `json:"dependencies"`
	OwnedNamespaces            []string                  `json:"owned_namespaces"`
	RequiredProtocols          []protocolRequirementJSON `json:"required_protocols"`
	RequiredEngineCapabilities []string                  `json:"required_engine_capabilities"`
	ProvidedCapabilities       []providedCapabilityJSON  `json:"provided_capabilities"`
	FrontendCompatibility      frontendCompatibilityJSON `json:"frontend_compatibility"`
}

type dependencyJSON struct {
	Alias                   string   `json:"alias"`
	AppID                   string   `json:"app_id"`
	MinimumVersionInclusive string   `json:"minimum_version_inclusive"`
	MaximumVersionExclusive string   `json:"maximum_version_exclusive"`
	RequiredCapabilities    []string `json:"required_capabilities"`
}

type protocolRequirementJSON struct {
	ProtocolID   string `json:"protocol_id"`
	Major        uint32 `json:"major"`
	MinimumMinor uint32 `json:"minimum_minor"`
}

type providedCapabilityJSON struct {
	CapabilityID                string   `json:"capability_id"`
	Kind                        string   `json:"kind"`
	HandlerRef                  *string  `json:"handler_ref,omitempty"`
	RequiredRuntimeCapabilities []string `json:"required_runtime_capabilities"`
}

type frontendCompatibilityJSON struct {
	Mode               string                   `json:"mode"`
	HostSDK            *protocolRequirementJSON `json:"host_sdk,omitempty"`
	ContributionSchema *protocolRequirementJSON `json:"contribution_schema,omitempty"`
}

type metadataBundleJSON struct {
	SchemaVersion string          `json:"schema_version"`
	Components    []componentJSON `json:"components"`
}

type componentJSON struct {
	ComponentID            string                     `json:"component_id"`
	ComponentKind          string                     `json:"component_kind"`
	ComponentFormatVersion uint32                     `json:"component_format_version"`
	NamespaceID            string                     `json:"namespace_id"`
	BusinessKey            string                     `json:"business_key"`
	DefinitionVersion      string                     `json:"definition_version"`
	LocalDependencies      []string                   `json:"local_dependencies"`
	ExternalComponentRefs  []externalComponentRefJSON `json:"external_component_refs"`
	OverridePolicy         string                     `json:"override_policy"`
	RemovalPolicy          string                     `json:"removal_policy"`
	ContentDigest          string                     `json:"content_digest"`
}

type externalComponentRefJSON struct {
	DependencyAlias string `json:"dependency_alias"`
	NamespaceID     string `json:"namespace_id"`
	ComponentKind   string `json:"component_kind"`
	BusinessKey     string `json:"business_key"`
}

func ParseAppManifest(raw []byte) (AppManifest, error) {
	var wire appManifestJSON
	if err := strictDecode(raw, &wire); err != nil {
		return AppManifest{}, fmt.Errorf("app manifest: %w", err)
	}
	if err := validateAppManifestJSONShape(raw, wire); err != nil {
		return AppManifest{}, fmt.Errorf("app manifest: %w", err)
	}
	for i, capability := range wire.ProvidedCapabilities {
		switch capability.Kind {
		case CapabilityKindMetadata:
			if capability.HandlerRef != nil {
				return AppManifest{}, fmt.Errorf("app manifest provided_capabilities[%d]: metadata handler_ref must be absent", i)
			}
		case CapabilityKindServiceHandler:
			if capability.HandlerRef == nil {
				return AppManifest{}, fmt.Errorf("app manifest provided_capabilities[%d]: service-handler handler_ref is required", i)
			}
		}
	}
	manifest := appManifestFromJSON(wire)
	if err := manifest.Validate(); err != nil {
		return AppManifest{}, err
	}
	return manifest, nil
}

func ParseMetadataBundle(raw []byte, manifest AppManifest) (MetadataBundle, error) {
	var wire metadataBundleJSON
	if err := strictDecode(raw, &wire); err != nil {
		return MetadataBundle{}, fmt.Errorf("metadata bundle: %w", err)
	}
	if err := validateMetadataBundleJSONShape(raw); err != nil {
		return MetadataBundle{}, fmt.Errorf("metadata bundle: %w", err)
	}
	bundle := metadataBundleFromJSON(wire)
	if err := bundle.Validate(manifest); err != nil {
		return MetadataBundle{}, err
	}
	return bundle, nil
}

func validateAppManifestJSONShape(raw []byte, wire appManifestJSON) error {
	object, err := rawObject(raw)
	if err != nil {
		return err
	}
	if err := requireExactFields(object, "schema_version", "app_id", "publisher_id", "semantic_version", "metadata_bundle_digest", "dependencies", "owned_namespaces", "required_protocols", "required_engine_capabilities", "provided_capabilities", "frontend_compatibility"); err != nil {
		return err
	}
	var dependencies []json.RawMessage
	if err := json.Unmarshal(object["dependencies"], &dependencies); err != nil {
		return err
	}
	for i, rawDependency := range dependencies {
		dependency, err := rawObject(rawDependency)
		if err != nil {
			return fmt.Errorf("dependencies[%d]: %w", i, err)
		}
		if err := requireExactFields(dependency, "alias", "app_id", "minimum_version_inclusive", "maximum_version_exclusive", "required_capabilities"); err != nil {
			return fmt.Errorf("dependencies[%d]: %w", i, err)
		}
	}
	var protocols []json.RawMessage
	if err := json.Unmarshal(object["required_protocols"], &protocols); err != nil {
		return err
	}
	for i, rawProtocol := range protocols {
		protocol, err := rawObject(rawProtocol)
		if err != nil {
			return fmt.Errorf("required_protocols[%d]: %w", i, err)
		}
		if err := requireExactFields(protocol, "protocol_id", "major", "minimum_minor"); err != nil {
			return fmt.Errorf("required_protocols[%d]: %w", i, err)
		}
	}
	var capabilities []json.RawMessage
	if err := json.Unmarshal(object["provided_capabilities"], &capabilities); err != nil {
		return err
	}
	for i, rawCapability := range capabilities {
		capability, err := rawObject(rawCapability)
		if err != nil {
			return fmt.Errorf("provided_capabilities[%d]: %w", i, err)
		}
		fields := []string{"capability_id", "kind", "required_runtime_capabilities"}
		if wire.ProvidedCapabilities[i].Kind == CapabilityKindServiceHandler {
			fields = append(fields, "handler_ref")
		}
		if err := requireExactFields(capability, fields...); err != nil {
			return fmt.Errorf("provided_capabilities[%d]: %w", i, err)
		}
	}
	frontend, err := rawObject(object["frontend_compatibility"])
	if err != nil {
		return fmt.Errorf("frontend_compatibility: %w", err)
	}
	frontendFields := []string{"mode"}
	if wire.FrontendCompatibility.Mode == FrontendModeRequired {
		frontendFields = append(frontendFields, "host_sdk", "contribution_schema")
	}
	if err := requireExactFields(frontend, frontendFields...); err != nil {
		return fmt.Errorf("frontend_compatibility: %w", err)
	}
	for _, name := range []string{"host_sdk", "contribution_schema"} {
		if rawProtocol, exists := frontend[name]; exists {
			protocol, err := rawObject(rawProtocol)
			if err != nil {
				return fmt.Errorf("frontend_compatibility %s: %w", name, err)
			}
			if err := requireExactFields(protocol, "protocol_id", "major", "minimum_minor"); err != nil {
				return fmt.Errorf("frontend_compatibility %s: %w", name, err)
			}
		}
	}
	return nil
}

func validateMetadataBundleJSONShape(raw []byte) error {
	object, err := rawObject(raw)
	if err != nil {
		return err
	}
	if err := requireExactFields(object, "schema_version", "components"); err != nil {
		return err
	}
	var components []json.RawMessage
	if err := json.Unmarshal(object["components"], &components); err != nil {
		return err
	}
	for i, rawComponent := range components {
		component, err := rawObject(rawComponent)
		if err != nil {
			return fmt.Errorf("components[%d]: %w", i, err)
		}
		if err := requireExactFields(component, "component_id", "component_kind", "component_format_version", "namespace_id", "business_key", "definition_version", "local_dependencies", "external_component_refs", "override_policy", "removal_policy", "content_digest"); err != nil {
			return fmt.Errorf("components[%d]: %w", i, err)
		}
		var refs []json.RawMessage
		if err := json.Unmarshal(component["external_component_refs"], &refs); err != nil {
			return fmt.Errorf("components[%d] external_component_refs: %w", i, err)
		}
		for j, rawRef := range refs {
			ref, err := rawObject(rawRef)
			if err != nil {
				return fmt.Errorf("components[%d] external_component_refs[%d]: %w", i, j, err)
			}
			if err := requireExactFields(ref, "dependency_alias", "namespace_id", "component_kind", "business_key"); err != nil {
				return fmt.Errorf("components[%d] external_component_refs[%d]: %w", i, j, err)
			}
		}
	}
	return nil
}

func rawObject(raw []byte) (map[string]json.RawMessage, error) {
	var object map[string]json.RawMessage
	if err := json.Unmarshal(raw, &object); err != nil {
		return nil, err
	}
	if object == nil {
		return nil, fmt.Errorf("object is required")
	}
	return object, nil
}

func requireExactFields(object map[string]json.RawMessage, fields ...string) error {
	if len(object) != len(fields) {
		return fmt.Errorf("object requires exactly fields %v", fields)
	}
	for _, field := range fields {
		raw, exists := object[field]
		if !exists {
			return fmt.Errorf("required field %q is absent", field)
		}
		if bytes.Equal(bytes.TrimSpace(raw), []byte("null")) {
			return fmt.Errorf("required field %q cannot be null", field)
		}
	}
	return nil
}

func strictDecode(raw []byte, target any) error {
	if len(bytes.TrimSpace(raw)) == 0 {
		return fmt.Errorf("JSON is required")
	}
	if err := validateJSONUnicode(raw); err != nil {
		return err
	}
	if err := rejectDuplicateMembers(raw); err != nil {
		return err
	}
	dec := json.NewDecoder(bytes.NewReader(raw))
	dec.DisallowUnknownFields()
	if err := dec.Decode(target); err != nil {
		return err
	}
	if err := ensureJSONEOF(dec); err != nil {
		return err
	}
	return nil
}

// encoding/json replaces malformed UTF-8 and unpaired UTF-16 surrogate
// escapes with U+FFFD. Package bytes are identity-bearing, so accepting that
// normalization would let distinct invalid inputs acquire the same canonical
// identity. Reject malformed Unicode before any encoding/json decode.
func validateJSONUnicode(raw []byte) error {
	if !utf8.Valid(raw) {
		return fmt.Errorf("JSON must contain valid UTF-8")
	}
	inString := false
	for i := 0; i < len(raw); i++ {
		if !inString {
			if raw[i] == '"' {
				inString = true
			}
			continue
		}
		if raw[i] == '"' {
			inString = false
			continue
		}
		if raw[i] != '\\' || i+1 >= len(raw) {
			continue
		}
		if raw[i+1] != 'u' {
			i++ // Skip an escaped quote or backslash as one string character.
			continue
		}
		first, ok := parseHexUTF16(raw, i+2)
		if !ok {
			continue // encoding/json owns ordinary escape syntax errors.
		}
		if utf16.IsSurrogate(rune(first)) {
			if first >= 0xdc00 {
				return fmt.Errorf("JSON contains an unpaired UTF-16 low surrogate escape")
			}
			if i+12 > len(raw) || raw[i+6] != '\\' || raw[i+7] != 'u' {
				return fmt.Errorf("JSON contains an unpaired UTF-16 high surrogate escape")
			}
			second, valid := parseHexUTF16(raw, i+8)
			if !valid || second < 0xdc00 || second > 0xdfff {
				return fmt.Errorf("JSON contains an unpaired UTF-16 high surrogate escape")
			}
			i += 11
			continue
		}
		i += 5
	}
	return nil
}

func parseHexUTF16(raw []byte, offset int) (uint16, bool) {
	if offset+4 > len(raw) {
		return 0, false
	}
	var value uint16
	for _, char := range raw[offset : offset+4] {
		value <<= 4
		switch {
		case char >= '0' && char <= '9':
			value |= uint16(char - '0')
		case char >= 'a' && char <= 'f':
			value |= uint16(char-'a') + 10
		case char >= 'A' && char <= 'F':
			value |= uint16(char-'A') + 10
		default:
			return 0, false
		}
	}
	return value, true
}

func ensureJSONEOF(dec *json.Decoder) error {
	var extra any
	if err := dec.Decode(&extra); err != io.EOF {
		if err == nil {
			return fmt.Errorf("multiple JSON values are forbidden")
		}
		return err
	}
	return nil
}

func rejectDuplicateMembers(raw []byte) error {
	dec := json.NewDecoder(bytes.NewReader(raw))
	dec.UseNumber()
	if err := scanJSONValue(dec); err != nil {
		return err
	}
	return ensureJSONEOF(dec)
}

func scanJSONValue(dec *json.Decoder) error {
	tok, err := dec.Token()
	if err != nil {
		return err
	}
	delim, ok := tok.(json.Delim)
	if !ok {
		return nil
	}
	switch delim {
	case '{':
		seen := map[string]struct{}{}
		for dec.More() {
			nameToken, err := dec.Token()
			if err != nil {
				return err
			}
			name, ok := nameToken.(string)
			if !ok {
				return fmt.Errorf("object member name must be a string")
			}
			if _, duplicate := seen[name]; duplicate {
				return fmt.Errorf("duplicate JSON member %q", name)
			}
			seen[name] = struct{}{}
			if err := scanJSONValue(dec); err != nil {
				return err
			}
		}
		_, err = dec.Token()
		return err
	case '[':
		for dec.More() {
			if err := scanJSONValue(dec); err != nil {
				return err
			}
		}
		_, err = dec.Token()
		return err
	default:
		return fmt.Errorf("unexpected JSON delimiter %q", delim)
	}
}

func appManifestFromJSON(w appManifestJSON) AppManifest {
	manifest := AppManifest{
		SchemaVersion: w.SchemaVersion, AppID: w.AppID, PublisherID: w.PublisherID,
		SemanticVersion: w.SemanticVersion, MetadataBundleDigest: w.MetadataBundleDigest,
		OwnedNamespaces: w.OwnedNamespaces, RequiredEngineCapabilities: w.RequiredEngineCapabilities,
		FrontendCompatibility: frontendCompatibilityFromJSON(w.FrontendCompatibility),
	}
	manifest.Dependencies = make([]Dependency, len(w.Dependencies))
	for i, dep := range w.Dependencies {
		manifest.Dependencies[i] = Dependency{Alias: dep.Alias, AppID: dep.AppID, MinimumVersionInclusive: dep.MinimumVersionInclusive, MaximumVersionExclusive: dep.MaximumVersionExclusive, RequiredCapabilities: dep.RequiredCapabilities}
	}
	manifest.RequiredProtocols = make([]ProtocolRequirement, len(w.RequiredProtocols))
	for i, requirement := range w.RequiredProtocols {
		manifest.RequiredProtocols[i] = protocolRequirementFromJSON(requirement)
	}
	manifest.ProvidedCapabilities = make([]ProvidedCapability, len(w.ProvidedCapabilities))
	for i, capability := range w.ProvidedCapabilities {
		handler := ""
		if capability.HandlerRef != nil {
			handler = *capability.HandlerRef
		}
		manifest.ProvidedCapabilities[i] = ProvidedCapability{CapabilityID: capability.CapabilityID, Kind: capability.Kind, HandlerRef: handler, RequiredRuntimeCapabilities: capability.RequiredRuntimeCapabilities}
	}
	return manifest
}

func frontendCompatibilityFromJSON(w frontendCompatibilityJSON) FrontendCompatibility {
	result := FrontendCompatibility{Mode: w.Mode}
	if w.HostSDK != nil {
		value := protocolRequirementFromJSON(*w.HostSDK)
		result.HostSDK = &value
	}
	if w.ContributionSchema != nil {
		value := protocolRequirementFromJSON(*w.ContributionSchema)
		result.ContributionSchema = &value
	}
	return result
}

func protocolRequirementFromJSON(w protocolRequirementJSON) ProtocolRequirement {
	return ProtocolRequirement{ProtocolID: w.ProtocolID, Major: w.Major, MinimumMinor: w.MinimumMinor}
}

func metadataBundleFromJSON(w metadataBundleJSON) MetadataBundle {
	bundle := MetadataBundle{SchemaVersion: w.SchemaVersion, Components: make([]Component, len(w.Components))}
	for i, component := range w.Components {
		refs := make([]ExternalComponentRef, len(component.ExternalComponentRefs))
		for j, ref := range component.ExternalComponentRefs {
			refs[j] = ExternalComponentRef{DependencyAlias: ref.DependencyAlias, NamespaceID: ref.NamespaceID, ComponentKind: ref.ComponentKind, BusinessKey: ref.BusinessKey}
		}
		bundle.Components[i] = Component{
			ComponentID: component.ComponentID, ComponentKind: component.ComponentKind,
			ComponentFormatVersion: component.ComponentFormatVersion, NamespaceID: component.NamespaceID,
			BusinessKey: component.BusinessKey, DefinitionVersion: component.DefinitionVersion,
			LocalDependencies: component.LocalDependencies, ExternalComponentRefs: refs,
			OverridePolicy: component.OverridePolicy,
			RemovalPolicy:  component.RemovalPolicy, ContentDigest: component.ContentDigest,
		}
	}
	return bundle
}
