package v1alpha2

import (
	"encoding/json"
	"fmt"
	"maps"
	"reflect"
	"strings"

	"k8s.io/kube-openapi/pkg/spec3"
	"k8s.io/kube-openapi/pkg/validation/spec"

	"github.com/grafana/grafana-app-sdk/app"
)

// ToManifestData is a function which converts this specific version of the AppManifestSpec (v1alpha2)
// to the generic app.ManifestData type for usage with an app.Manifest.
// nolint:gocognit,funlen,gocyclo
func (s *AppManifestSpec) ToManifestData() (app.ManifestData, error) {
	data := app.ManifestData{
		AppName:        s.AppName,
		AppDisplayName: s.AppDisplayName,
		Group:          s.Group,
		Versions:       make([]app.ManifestVersion, len(s.Versions)),
	}
	// Versions
	for idx, version := range s.Versions {
		v := app.ManifestVersion{
			Name:   version.Name,
			Served: true,
			Kinds:  make([]app.ManifestVersionKind, len(version.Kinds)),
		}
		if version.Served != nil {
			v.Served = *version.Served
		}

		for kidx, kind := range version.Kinds {
			k := app.ManifestVersionKind{
				Kind:             kind.Kind,
				Plural:           strings.ToLower(kind.Kind) + "s",
				SelectableFields: kind.SelectableFields,
				Scope:            string(kind.Scope),
			}
			if kind.UserReadable != nil {
				k.UserReadable = *kind.UserReadable
			}
			k.FolderScoped = kind.FolderScoped
			if kind.Plural != nil {
				k.Plural = strings.ToLower(*kind.Plural)
			}
			if kind.Conversion != nil {
				k.Conversion = *kind.Conversion
			}
			// Admission
			if kind.Admission != nil {
				adm := app.AdmissionCapabilities{}
				if kind.Admission.Validation != nil {
					adm.Validation = &app.ValidationCapability{
						Operations: make([]app.AdmissionOperation, len(kind.Admission.Validation.Operations)),
					}
					for oidx, op := range kind.Admission.Validation.Operations {
						adm.Validation.Operations[oidx] = app.AdmissionOperation(op) // TODO: make this case-insensitive?
					}
				}
				if kind.Admission.Mutation != nil {
					adm.Mutation = &app.MutationCapability{
						Operations: make([]app.AdmissionOperation, len(kind.Admission.Mutation.Operations)),
					}
					for oidx, op := range kind.Admission.Mutation.Operations {
						adm.Mutation.Operations[oidx] = app.AdmissionOperation(op) // TODO: make this case-insensitive?
					}
				}
				k.Admission = &adm
			}
			// PrinterColumns
			if kind.AdditionalPrinterColumns != nil {
				k.AdditionalPrinterColumns = make([]app.ManifestVersionKindAdditionalPrinterColumn, len(kind.AdditionalPrinterColumns))
				for i, col := range kind.AdditionalPrinterColumns {
					translated := app.ManifestVersionKindAdditionalPrinterColumn{
						Name:     col.Name,
						Type:     col.Type,
						JSONPath: col.JsonPath,
					}
					if col.Format != nil {
						translated.Format = *col.Format
					}
					if col.Description != nil {
						translated.Description = *col.Description
					}
					if col.Priority != nil {
						copied := *col.Priority
						translated.Priority = &copied
					}
					k.AdditionalPrinterColumns[i] = translated
				}
			}
			// SearchFields
			if len(kind.SearchFields) > 0 {
				k.SearchFields = make([]app.ManifestVersionKindSearchField, len(kind.SearchFields))
				for i, sf := range kind.SearchFields {
					translated := app.ManifestVersionKindSearchField{
						Name:         sf.Name,
						Type:         string(sf.Type),
						Capabilities: make([]string, len(sf.Capabilities)),
					}
					for ci, c := range sf.Capabilities {
						translated.Capabilities[ci] = string(c)
					}
					if sf.Path != nil {
						translated.Path = *sf.Path
					}
					if sf.Array != nil {
						translated.Array = *sf.Array
					}
					if sf.EmitZeroIfAbsent != nil {
						translated.EmitZeroIfAbsent = *sf.EmitZeroIfAbsent
					}
					if sf.Description != nil {
						translated.Description = *sf.Description
					}
					k.SearchFields[i] = translated
				}
			}
			// Schema
			if kind.Schemas != nil {
				toParse := make(map[string]any)
				maps.Copy(toParse, kind.Schemas)
				if _, ok := toParse[kind.Kind]; !ok && len(toParse) > 0 {
					return app.ManifestData{}, fmt.Errorf("schemas for %v must contain an entry named '%v'", kind.Kind, kind.Kind)
				}

				var err error
				k.Schema, err = app.VersionSchemaFromMap(toParse, k.Kind)
				if err != nil {
					return app.ManifestData{}, err
				}
			}
			// Routes
			if len(kind.Routes) > 0 {
				k.Routes = make(map[string]spec3.PathProps)
				marshaled, err := json.Marshal(kind.Routes)
				if err != nil {
					return app.ManifestData{}, err
				}
				err = json.Unmarshal(marshaled, &k.Routes)
				if err != nil {
					return app.ManifestData{}, err
				}
			}
			v.Kinds[kidx] = k
		}

		// Routes
		if version.Routes != nil {
			if len(version.Routes.Namespaced) > 0 {
				v.Routes.Namespaced = make(map[string]spec3.PathProps)
				marshaled, err := json.Marshal(version.Routes.Namespaced)
				if err != nil {
					return app.ManifestData{}, err
				}
				err = json.Unmarshal(marshaled, &v.Routes.Namespaced)
				if err != nil {
					return app.ManifestData{}, err
				}
			}
			if len(version.Routes.Cluster) > 0 {
				v.Routes.Cluster = make(map[string]spec3.PathProps)
				marshaled, err := json.Marshal(version.Routes.Cluster)
				if err != nil {
					return app.ManifestData{}, err
				}
				err = json.Unmarshal(marshaled, &v.Routes.Cluster)
				if err != nil {
					return app.ManifestData{}, err
				}
			}
			if len(version.Routes.Schemas) > 0 {
				v.Routes.Schemas = make(map[string]spec.Schema)
				marshaled, err := json.Marshal(version.Routes.Schemas)
				if err != nil {
					return app.ManifestData{}, err
				}
				err = json.Unmarshal(marshaled, &v.Routes.Schemas)
				if err != nil {
					return app.ManifestData{}, err
				}
			}
		}

		data.Versions[idx] = v
	}
	if s.PreferredVersion != nil {
		data.PreferredVersion = *s.PreferredVersion
	} else if len(s.Versions) > 0 {
		data.PreferredVersion = s.Versions[len(s.Versions)-1].Name
	}
	// Permissions
	if s.ExtraPermissions != nil && s.ExtraPermissions.AccessKinds != nil {
		data.ExtraPermissions = &app.Permissions{
			AccessKinds: make([]app.KindPermission, len(s.ExtraPermissions.AccessKinds)),
		}
		for idx, access := range s.ExtraPermissions.AccessKinds {
			perm := app.KindPermission{
				Group:    access.Group,
				Resource: access.Resource,
				Actions:  make([]app.KindPermissionAction, len(access.Actions)),
			}
			for aidx, action := range access.Actions {
				perm.Actions[aidx] = app.KindPermissionAction(action)
			}
			data.ExtraPermissions.AccessKinds[idx] = perm
		}
	}
	// Operator Info
	if s.Operator != nil {
		data.Operator = &app.ManifestOperatorInfo{}
		if s.Operator.Url != nil {
			data.Operator.URL = *s.Operator.Url
		}
		if s.Operator.Webhooks != nil {
			webhooks := app.ManifestOperatorWebhookProperties{}
			if s.Operator.Webhooks.ConversionPath != nil {
				webhooks.ConversionPath = *s.Operator.Webhooks.ConversionPath
			}
			if s.Operator.Webhooks.ValidationPath != nil {
				webhooks.ValidationPath = *s.Operator.Webhooks.ValidationPath
			}
			if s.Operator.Webhooks.MutationPath != nil {
				webhooks.MutationPath = *s.Operator.Webhooks.MutationPath
			}
			data.Operator.Webhooks = &webhooks
		}
	}
	// Roles and RoleBindings
	if s.Roles != nil {
		data.Roles = make(map[string]app.ManifestRole)
		for k, v := range s.Roles {
			converted := app.ManifestRole{
				Title:       v.Title,
				Description: v.Description,
				Kinds:       make([]app.ManifestRoleKind, len(v.Kinds)),
				Routes:      make([]string, len(v.Routes)),
			}
			for idx, k2 := range v.Kinds {
				switch knd := k2.(type) {
				case AppManifestRoleKindWithPermissionSet:
					perms := string(knd.PermissionSet)
					converted.Kinds[idx] = app.ManifestRoleKind{
						Kind:          knd.Kind,
						PermissionSet: &perms,
					}
				case AppManifestRoleKindWithVerbs:
					verbs := make([]string, len(knd.Verbs))
					copy(verbs, knd.Verbs)
					converted.Kinds[idx] = app.ManifestRoleKind{
						Kind:  knd.Kind,
						Verbs: verbs,
					}
				case map[string]any:
					kindVal, ok := knd["kind"]
					if !ok {
						return app.ManifestData{}, fmt.Errorf("unable to parse roles: missing kind in role %s, kind index %d", k, idx)
					}
					kindStr, ok := kindVal.(string)
					if !ok {
						return app.ManifestData{}, fmt.Errorf("unable to parse roles: invalid kind in role %s, kind index %d (expected 'kind' type string, got %v)", k, idx, reflect.TypeOf(kindVal))
					}
					roleKind := app.ManifestRoleKind{
						Kind: kindStr,
					}
					if permSetVal, ok := knd["permissionSet"]; ok {
						str, ok := permSetVal.(string)
						if !ok {
							return app.ManifestData{}, fmt.Errorf("unable to parse roles: invalid kind in role %s, kind index %d (expected 'permissionSet' type string, got %v)", k, idx, reflect.TypeOf(permSetVal))
						}
						roleKind.PermissionSet = &str
					} else if verbVals, ok := knd["verbs"]; ok {
						verbs, ok := verbVals.([]any)
						if !ok {
							return app.ManifestData{}, fmt.Errorf("unable to parse roles: invalid kind in role %s, kind index %d (expected 'verbs' type []any, got %v)", k, idx, reflect.TypeOf(verbVals))
						}
						roleKind.Verbs = make([]string, len(verbs))
						for vidx, verb := range verbs {
							v, ok := verb.(string)
							if !ok {
								return app.ManifestData{}, fmt.Errorf("unable to parse roles: invalid kind in role %s, kind index %d, verbs index %d (expected element type 'string', got %v)", k, idx, vidx, reflect.TypeOf(verb))
							}
							roleKind.Verbs[vidx] = v
						}
					}
					converted.Kinds[idx] = roleKind
				default:
					return app.ManifestData{}, fmt.Errorf("unable to parse roles: invalid kind type %v for role %s, kind index %d", reflect.TypeOf(knd), k, idx)
				}
			}
			copy(converted.Routes, v.Routes)
			data.Roles[k] = converted
		}
	}
	if s.RoleBindings != nil {
		data.RoleBindings = &app.ManifestRoleBindings{
			Viewer:     s.RoleBindings.Viewer,
			Editor:     s.RoleBindings.Editor,
			Admin:      s.RoleBindings.Admin,
			Additional: s.RoleBindings.Additional,
		}
	}
	return data, data.Validate()
}

// SpecFromManifestData is a function which converts an instance of app.ManifestData
// to this specific version of the AppManifestSpec (v1alpha1).
// nolint:gocognit,funlen,gocyclo
func SpecFromManifestData(data app.ManifestData) (*AppManifestSpec, error) {
	manifestSpec := AppManifestSpec{
		AppName:        data.AppName,
		AppDisplayName: data.AppDisplayName,
		Group:          data.Group,
		Versions:       make([]AppManifestManifestVersion, 0),
	}
	if data.PreferredVersion != "" {
		manifestSpec.PreferredVersion = &data.PreferredVersion
	}
	// Versions
	for _, version := range data.Versions {
		ver := AppManifestManifestVersion{
			Name:   version.Name,
			Served: &version.Served,
			Kinds:  make([]AppManifestManifestVersionKind, 0),
		}
		for _, kind := range version.Kinds {
			k := AppManifestManifestVersionKind{
				Kind:             kind.Kind,
				Scope:            AppManifestManifestVersionKindScope(kind.Scope),
				SelectableFields: kind.SelectableFields,
				Conversion:       &kind.Conversion,
				UserReadable:     &kind.UserReadable,
				FolderScoped:     kind.FolderScoped,
			}
			if kind.Schema != nil {
				k.Schemas = kind.Schema.AsOpenAPI3SchemasMap()
			}
			if kind.Plural != "" {
				k.Plural = &kind.Plural
			}
			if kind.Admission != nil {
				k.Admission = &AppManifestAdmissionCapabilities{}
				if kind.Admission.Mutation != nil {
					k.Admission.Mutation = &AppManifestMutationCapability{
						Operations: make([]AppManifestAdmissionOperation, len(kind.Admission.Mutation.Operations)),
					}
					for i := 0; i < len(kind.Admission.Mutation.Operations); i++ {
						k.Admission.Mutation.Operations[i] = AppManifestAdmissionOperation(kind.Admission.Mutation.Operations[i])
					}
				}
				if kind.Admission.Validation != nil {
					k.Admission.Validation = &AppManifestValidationCapability{
						Operations: make([]AppManifestAdmissionOperation, len(kind.Admission.Validation.Operations)),
					}
					for i := 0; i < len(kind.Admission.Validation.Operations); i++ {
						k.Admission.Validation.Operations[i] = AppManifestAdmissionOperation(kind.Admission.Validation.Operations[i])
					}
				}
			}
			if len(kind.AdditionalPrinterColumns) > 0 {
				k.AdditionalPrinterColumns = make([]AppManifestAdditionalPrinterColumns, len(kind.AdditionalPrinterColumns))
				for i := 0; i < len(kind.AdditionalPrinterColumns); i++ {
					k.AdditionalPrinterColumns[i] = AppManifestAdditionalPrinterColumns{
						Name:        kind.AdditionalPrinterColumns[i].Name,
						Type:        kind.AdditionalPrinterColumns[i].Type,
						Format:      &kind.AdditionalPrinterColumns[i].Format,
						Description: &kind.AdditionalPrinterColumns[i].Description,
						Priority:    kind.AdditionalPrinterColumns[i].Priority,
						JsonPath:    kind.AdditionalPrinterColumns[i].JSONPath,
					}
				}
			}
			if len(kind.SearchFields) > 0 {
				k.SearchFields = make([]AppManifestSearchField, len(kind.SearchFields))
				for i, sf := range kind.SearchFields {
					translated := AppManifestSearchField{
						Name:         sf.Name,
						Type:         AppManifestSearchFieldType(sf.Type),
						Capabilities: make([]AppManifestSearchFieldCapabilities, len(sf.Capabilities)),
					}
					for ci, c := range sf.Capabilities {
						translated.Capabilities[ci] = AppManifestSearchFieldCapabilities(c)
					}
					if sf.Path != "" {
						translated.Path = &sf.Path
					}
					if sf.Array {
						translated.Array = &sf.Array
					}
					if sf.EmitZeroIfAbsent {
						translated.EmitZeroIfAbsent = &sf.EmitZeroIfAbsent
					}
					if sf.Description != "" {
						translated.Description = &sf.Description
					}
					k.SearchFields[i] = translated
				}
			}
			// Routes
			if kind.Routes != nil {
				k.Routes = make(map[string]any)
				for path := range kind.Routes {
					k.Routes[path] = kind.Routes[path]
				}
			}
			ver.Kinds = append(ver.Kinds, k)
		}
		// Routes
		if len(version.Routes.Namespaced) > 0 || len(version.Routes.Cluster) > 0 {
			ver.Routes = NewAppManifestManifestVersionRoutes()
			if len(version.Routes.Namespaced) > 0 {
				ver.Routes.Namespaced = make(map[string]any)
				for path := range version.Routes.Namespaced {
					ver.Routes.Namespaced[path] = version.Routes.Namespaced[path]
				}
			}
			if len(version.Routes.Cluster) > 0 {
				ver.Routes.Cluster = make(map[string]any)
				for path := range version.Routes.Cluster {
					ver.Routes.Cluster[path] = version.Routes.Cluster[path]
				}
			}
			if len(version.Routes.Schemas) > 0 {
				ver.Routes.Schemas = make(map[string]any)
				for path := range version.Routes.Schemas {
					ver.Routes.Schemas[path] = version.Routes.Schemas[path]
				}
			}
		}
		manifestSpec.Versions = append(manifestSpec.Versions, ver)
	}
	// Permissions
	if data.ExtraPermissions != nil && data.ExtraPermissions.AccessKinds != nil {
		manifestSpec.ExtraPermissions = &AppManifestV1alpha2SpecExtraPermissions{
			AccessKinds: make([]AppManifestKindPermission, len(data.ExtraPermissions.AccessKinds)),
		}
		for idx, access := range data.ExtraPermissions.AccessKinds {
			perm := AppManifestKindPermission{
				Group:    access.Group,
				Resource: access.Resource,
				Actions:  make([]string, len(access.Actions)),
			}
			for aidx, action := range access.Actions {
				perm.Actions[aidx] = string(action)
			}
			manifestSpec.ExtraPermissions.AccessKinds[idx] = perm
		}
	}
	// Operator Info
	if data.Operator != nil {
		manifestSpec.Operator = &AppManifestOperatorInfo{
			Url: &data.Operator.URL,
		}
		if data.Operator.Webhooks != nil {
			manifestSpec.Operator.Webhooks = &AppManifestOperatorWebhookProperties{
				ConversionPath: &data.Operator.Webhooks.ConversionPath,
				ValidationPath: &data.Operator.Webhooks.ValidationPath,
				MutationPath:   &data.Operator.Webhooks.MutationPath,
			}
		}
	}
	// Roles and RoleBindings
	if data.Roles != nil {
		manifestSpec.Roles = make(map[string]AppManifestRole)
		for k, v := range data.Roles {
			converted := AppManifestRole{
				Title:       v.Title,
				Description: v.Description,
				Kinds:       make([]AppManifestRoleKind, len(v.Kinds)),
				Routes:      make([]string, len(v.Routes)),
			}
			for idx, knd := range v.Kinds {
				if knd.PermissionSet != nil {
					converted.Kinds[idx] = AppManifestRoleKindWithPermissionSet{
						Kind:          knd.Kind,
						PermissionSet: AppManifestRoleKindWithPermissionSetPermissionSet(*knd.PermissionSet),
					}
					continue
				}
				verbs := make([]string, len(knd.Verbs))
				copy(verbs, knd.Verbs)
				converted.Kinds[idx] = AppManifestRoleKindWithVerbs{
					Kind:  knd.Kind,
					Verbs: verbs,
				}
			}
			copy(converted.Routes, v.Routes)
			manifestSpec.Roles[k] = converted
		}
	}
	if data.RoleBindings != nil {
		manifestSpec.RoleBindings = &AppManifestV1alpha2SpecRoleBindings{
			Viewer:     data.RoleBindings.Viewer,
			Editor:     data.RoleBindings.Editor,
			Admin:      data.RoleBindings.Admin,
			Additional: data.RoleBindings.Additional,
		}
	}

	return &manifestSpec, nil
}
