package sdk

import (
	"fmt"
	"sort"
	"strings"
)

const (
	ContractFamilyProfile    = "profile"
	ContractFamilyArtifacts  = "artifacts"
	ContractFamilyParameters = "parameters"
	ContractFamilyResources  = "resources"
	ContractFamilyStorage    = "storage"
	ContractFamilyNetwork    = "network"
	ContractFamilyExecution  = "execution"
	ContractFamilyOutputs    = "outputs"
	ContractFamilyValidation = "validation"
)

var requiredLaunchableOCIContractFamilies = []string{
	ContractFamilyProfile,
	ContractFamilyArtifacts,
	ContractFamilyParameters,
	ContractFamilyResources,
	ContractFamilyStorage,
	ContractFamilyNetwork,
	ContractFamilyExecution,
	ContractFamilyOutputs,
	ContractFamilyValidation,
}

type ManifestFinding struct {
	Family  string
	Path    string
	Message string
}

type ManifestContractReport struct {
	Kind      string
	Slug      string
	Passed    bool
	Families  []string
	Findings  []ManifestFinding
	Endpoints []string
}

func ValidateLaunchableOCIManifest(manifest map[string]any) ManifestContractReport {
	report := ManifestContractReport{
		Families: append([]string(nil), requiredLaunchableOCIContractFamilies...),
		Passed:   true,
	}
	for _, family := range requiredLaunchableOCIContractFamilies {
		if _, ok := objectAt(manifest, family); !ok {
			report.add(family, family, "contract family is required")
		}
	}

	profile, _ := objectAt(manifest, ContractFamilyProfile)
	report.Kind = stringAt(profile, "kind")
	report.Slug = stringAt(profile, "slug")
	if report.Kind != "gpuaas.launchable_oci_workload" {
		report.add(ContractFamilyProfile, "profile.kind", "must be gpuaas.launchable_oci_workload")
	}
	if stringAt(profile, "schema_version") != "v1" {
		report.add(ContractFamilyProfile, "profile.schema_version", "must be v1")
	}
	if report.Slug == "" {
		report.add(ContractFamilyProfile, "profile.slug", "is required")
	}
	if stringAt(profile, "display_name") == "" {
		report.add(ContractFamilyProfile, "profile.display_name", "is required")
	}
	if stringAt(profile, "launch_mode") != "existing_allocation" {
		report.add(ContractFamilyProfile, "profile.launch_mode", "must be existing_allocation")
	}

	artifacts, _ := objectAt(manifest, ContractFamilyArtifacts)
	primaryImage, ok := objectAt(artifacts, "primary_image")
	if !ok {
		report.add(ContractFamilyArtifacts, "artifacts.primary_image", "is required")
	} else {
		if stringAt(primaryImage, "source") != "platform_registry" {
			report.add(ContractFamilyArtifacts, "artifacts.primary_image.source", "must be platform_registry")
		}
		if stringAt(primaryImage, "artifact_name") == "" {
			report.add(ContractFamilyArtifacts, "artifacts.primary_image.artifact_name", "is required")
		}
		if boolAt(primaryImage, "digest_required") != true {
			report.add(ContractFamilyArtifacts, "artifacts.primary_image.digest_required", "must be true")
		}
	}

	parameters, _ := objectAt(manifest, ContractFamilyParameters)
	schema, ok := objectAt(parameters, "schema")
	if !ok {
		report.add(ContractFamilyParameters, "parameters.schema", "is required")
	} else {
		if stringAt(schema, "type") != "object" {
			report.add(ContractFamilyParameters, "parameters.schema.type", "must be object")
		}
		if _, ok := objectAt(schema, "properties"); !ok {
			report.add(ContractFamilyParameters, "parameters.schema.properties", "is required")
		}
	}

	resources, _ := objectAt(manifest, ContractFamilyResources)
	if len(resources) == 0 {
		report.add(ContractFamilyResources, "resources", "must declare at least one resource family")
	}

	storage, _ := objectAt(manifest, ContractFamilyStorage)
	if mounts := sliceAt(storage, "mounts"); len(mounts) == 0 {
		report.add(ContractFamilyStorage, "storage.mounts", "must declare at least one mount")
	}

	network, _ := objectAt(manifest, ContractFamilyNetwork)
	endpoints := sliceAt(network, "endpoints")
	if len(endpoints) == 0 {
		report.add(ContractFamilyNetwork, "network.endpoints", "must declare at least one endpoint")
	}
	endpointNames := map[string]struct{}{}
	for i, raw := range endpoints {
		endpoint, ok := raw.(map[string]any)
		if !ok {
			report.add(ContractFamilyNetwork, fmt.Sprintf("network.endpoints[%d]", i), "must be an object")
			continue
		}
		name := stringAt(endpoint, "name")
		if name == "" {
			report.add(ContractFamilyNetwork, fmt.Sprintf("network.endpoints[%d].name", i), "is required")
		} else {
			endpointNames[name] = struct{}{}
			report.Endpoints = append(report.Endpoints, name)
		}
		if numberAt(endpoint, "port") <= 0 {
			report.add(ContractFamilyNetwork, fmt.Sprintf("network.endpoints[%d].port", i), "must be a positive port")
		}
		if stringAt(endpoint, "type") == "" {
			report.add(ContractFamilyNetwork, fmt.Sprintf("network.endpoints[%d].type", i), "is required")
		}
		if stringAt(endpoint, "auth_pattern") == "" {
			report.add(ContractFamilyNetwork, fmt.Sprintf("network.endpoints[%d].auth_pattern", i), "is required")
		}
		if managed, ok := objectAt(endpoint, "managed_ingress"); ok && boolAt(managed, "enabled") {
			if stringAt(managed, "client_auth_mode") == "" {
				report.add(ContractFamilyNetwork, fmt.Sprintf("network.endpoints[%d].managed_ingress.client_auth_mode", i), "is required when managed ingress is enabled")
			}
			if stringAt(managed, "route_family") == "" {
				report.add(ContractFamilyNetwork, fmt.Sprintf("network.endpoints[%d].managed_ingress.route_family", i), "is required when managed ingress is enabled")
			}
		}
	}
	sort.Strings(report.Endpoints)

	execution, _ := objectAt(manifest, ContractFamilyExecution)
	defaultEngine := stringAt(execution, "default_engine")
	if defaultEngine == "" {
		report.add(ContractFamilyExecution, "execution.default_engine", "is required")
	}
	supportedEngines := sliceAt(execution, "supported_engines")
	if len(supportedEngines) == 0 {
		report.add(ContractFamilyExecution, "execution.supported_engines", "must declare at least one engine")
	}
	if defaultEngine != "" && !supportedEngineExists(supportedEngines, defaultEngine) {
		report.add(ContractFamilyExecution, "execution.supported_engines", "must include execution.default_engine")
	}

	outputs, _ := objectAt(manifest, ContractFamilyOutputs)
	outputEndpoints := namedReferenceSlice(outputs, "endpoints")
	if len(outputEndpoints) == 0 {
		report.add(ContractFamilyOutputs, "outputs.endpoints", "must expose at least one connect action endpoint")
	}
	for _, ref := range outputEndpoints {
		if _, ok := endpointNames[ref]; !ok {
			report.add(ContractFamilyOutputs, "outputs.endpoints", "must reference declared network endpoints")
		}
	}

	validation, _ := objectAt(manifest, ContractFamilyValidation)
	readiness := sliceAt(validation, "readiness")
	if len(readiness) == 0 {
		report.add(ContractFamilyValidation, "validation.readiness", "must declare at least one readiness check")
	}
	for i, raw := range readiness {
		check, ok := raw.(map[string]any)
		if !ok {
			report.add(ContractFamilyValidation, fmt.Sprintf("validation.readiness[%d]", i), "must be an object")
			continue
		}
		endpoint := stringAt(check, "endpoint")
		if endpoint == "" {
			report.add(ContractFamilyValidation, fmt.Sprintf("validation.readiness[%d].endpoint", i), "is required")
			continue
		}
		if _, ok := endpointNames[endpoint]; !ok {
			report.add(ContractFamilyValidation, fmt.Sprintf("validation.readiness[%d].endpoint", i), "must reference a declared network endpoint")
		}
	}

	return report
}

func (r *ManifestContractReport) add(family, path, message string) {
	r.Passed = false
	r.Findings = append(r.Findings, ManifestFinding{Family: family, Path: path, Message: message})
}

func objectAt(values map[string]any, key string) (map[string]any, bool) {
	raw, ok := values[key]
	if !ok {
		return nil, false
	}
	typed, ok := raw.(map[string]any)
	return typed, ok
}

func sliceAt(values map[string]any, key string) []any {
	raw, ok := values[key]
	if !ok {
		return nil
	}
	typed, ok := raw.([]any)
	if !ok {
		return nil
	}
	return typed
}

func stringAt(values map[string]any, key string) string {
	raw, ok := values[key]
	if !ok {
		return ""
	}
	typed, ok := raw.(string)
	if !ok {
		return ""
	}
	return strings.TrimSpace(typed)
}

func boolAt(values map[string]any, key string) bool {
	raw, ok := values[key]
	if !ok {
		return false
	}
	typed, ok := raw.(bool)
	return ok && typed
}

func numberAt(values map[string]any, key string) float64 {
	raw, ok := values[key]
	if !ok {
		return 0
	}
	switch typed := raw.(type) {
	case float64:
		return typed
	case int:
		return float64(typed)
	default:
		return 0
	}
}

func supportedEngineExists(values []any, engine string) bool {
	for _, raw := range values {
		item, ok := raw.(map[string]any)
		if ok && stringAt(item, "engine") == engine {
			return true
		}
	}
	return false
}

func namedReferenceSlice(values map[string]any, key string) []string {
	var refs []string
	for _, raw := range sliceAt(values, key) {
		item, ok := raw.(map[string]any)
		if !ok {
			continue
		}
		name := stringAt(item, "name")
		if name != "" {
			refs = append(refs, name)
		}
	}
	return refs
}
