// Copyright 2025 Microsoft Corporation
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package coreapi

import (
	"fmt"
	"path/filepath"
	"strings"
	"sync"
	"time"

	"github.com/google/go-cmp/cmp"
	"github.com/google/go-cmp/cmp/cmpopts"

	"k8s.io/apimachinery/pkg/api/equality"
	"k8s.io/utils/set"

	azcorearm "github.com/Azure/azure-sdk-for-go/sdk/azcore/arm"

	"github.com/Azure/ARO-HCP/internal/api/metadataapi"
)

var (
	// we have some private fields that cmp.Diff chokes on. This skips them.
	// Use only for error messages unless you know what you're doing.
	CmpDiffOptions = []cmp.Option{
		cmpopts.IgnoreFields(azcorearm.ResourceID{}, "ResourceType"),
		cmpopts.IgnoreFields(azcorearm.ResourceID{}, "isChild"),
		cmpopts.IgnoreFields(azcorearm.ResourceID{}, "stringValue"),
		cmpopts.IgnoreFields(metadataapi.InternalID{}, "path"),
		cmpopts.IgnoreFields(metadataapi.InternalID{}, "kind"),
	}
)

func init() {
	// we need some semantic equalities added for our types and this is the standard location
	metadataapi.Must("",
		equality.Semantic.AddFuncs(
			func(a, b time.Time) bool {
				return a.UTC().Equal(b.UTC())
			},
			func(a, b azcorearm.ResourceID) bool {
				return strings.EqualFold(a.String(), b.String())
			},
			func(a, b azcorearm.ResourceType) bool {
				return strings.EqualFold(a.String(), b.String())
			},
			func(a, b metadataapi.InternalID) bool {
				return a.String() == b.String()
			},
		),
	)
}

const (
	ProviderNamespace                               = "Microsoft.RedHatOpenShift"
	ClusterResourceTypeName                         = "hcpOpenShiftClusters"
	ServiceProviderClusterResourceTypeName          = "serviceProviderClusters"
	ServiceProviderNodePoolResourceTypeName         = "serviceProviderNodePools"
	VersionResourceTypeName                         = "hcpOpenShiftVersions"
	NodePoolResourceTypeName                        = "nodePools"
	ExternalAuthResourceTypeName                    = "externalAuths"
	OperationResultResourceTypeName                 = "hcpOperationResults"
	OperationStatusResourceTypeName                 = "hcpOperationStatuses"
	ControllerResourceTypeName                      = "hcpOpenShiftControllers"
	RequestAdminCredentialActionTypeName            = "requestadmincredential"
	RevokeAdminCredentialsActionTypeName            = "revokecredentials"
	ManagementClusterContentResourceTypeName        = "managementClusterContents"
	SystemAdminCredentialRequestResourceTypeName    = "systemAdminCredentialRequests"
	SystemAdminCredentialRevocationResourceTypeName = "systemAdminCredentialRevocations"
)

var (
	OperationStatusResourceType         = azcorearm.NewResourceType(ProviderNamespace, OperationStatusResourceTypeName)
	ClusterResourceType                 = azcorearm.NewResourceType(ProviderNamespace, ClusterResourceTypeName)
	ServiceProviderClusterResourceType  = azcorearm.NewResourceType(ProviderNamespace, ClusterResourceTypeName+"/"+ServiceProviderClusterResourceTypeName)
	NodePoolResourceType                = azcorearm.NewResourceType(ProviderNamespace, ClusterResourceTypeName+"/"+NodePoolResourceTypeName)
	ServiceProviderNodePoolResourceType = azcorearm.NewResourceType(ProviderNamespace, filepath.Join(ClusterResourceTypeName, NodePoolResourceTypeName, ServiceProviderNodePoolResourceTypeName))
	ExternalAuthResourceType            = azcorearm.NewResourceType(ProviderNamespace, ClusterResourceTypeName+"/"+ExternalAuthResourceTypeName)
	PreflightResourceType               = azcorearm.NewResourceType(ProviderNamespace, "deployments/preflight")
	VersionResourceType                 = azcorearm.NewResourceType(ProviderNamespace, "locations/"+VersionResourceTypeName)
	ClusterControllerResourceType       = azcorearm.NewResourceType(ProviderNamespace, filepath.Join(ClusterResourceTypeName, ControllerResourceTypeName))
	NodePoolControllerResourceType      = azcorearm.NewResourceType(ProviderNamespace, filepath.Join(ClusterResourceTypeName, NodePoolResourceTypeName, ControllerResourceTypeName))
	ExternalAuthControllerResourceType  = azcorearm.NewResourceType(ProviderNamespace, filepath.Join(ClusterResourceTypeName, ExternalAuthResourceTypeName, ControllerResourceTypeName))
	RequestAdminCredentialActionType    = azcorearm.NewResourceType(ProviderNamespace, filepath.Join(ClusterResourceTypeName, RequestAdminCredentialActionTypeName))
	RevokeAdminCredentialsActionType    = azcorearm.NewResourceType(ProviderNamespace, filepath.Join(ClusterResourceTypeName, RevokeAdminCredentialsActionTypeName))
	// ClusterScopedManagementClusterContentResourceType is managementClusterContents nested directly under a Cluster
	ClusterScopedManagementClusterContentResourceType = azcorearm.NewResourceType(ProviderNamespace, filepath.Join(ClusterResourceTypeName, ManagementClusterContentResourceTypeName))
	// NodePoolScopedManagementClusterContentResourceType is managementClusterContents nested under a Node Pool
	NodePoolScopedManagementClusterContentResourceType = azcorearm.NewResourceType(ProviderNamespace, filepath.Join(ClusterResourceTypeName, NodePoolResourceTypeName, ManagementClusterContentResourceTypeName))
	// SystemAdminCredentialRequestResourceType is systemAdminCredentialRequests nested directly under a Cluster
	SystemAdminCredentialRequestResourceType = azcorearm.NewResourceType(ProviderNamespace, ClusterResourceTypeName+"/"+SystemAdminCredentialRequestResourceTypeName)
	// SystemAdminCredentialRequestControllerResourceType is controllers nested under systemAdminCredentialRequests
	SystemAdminCredentialRequestControllerResourceType = azcorearm.NewResourceType(ProviderNamespace, filepath.Join(ClusterResourceTypeName, SystemAdminCredentialRequestResourceTypeName, ControllerResourceTypeName))
	// SystemAdminCredentialRevocationResourceType is systemAdminCredentialRevocations nested directly under a Cluster
	SystemAdminCredentialRevocationResourceType = azcorearm.NewResourceType(ProviderNamespace, ClusterResourceTypeName+"/"+SystemAdminCredentialRevocationResourceTypeName)
	// SystemAdminCredentialRevocationControllerResourceType is controllers nested under systemAdminCredentialRevocations
	SystemAdminCredentialRevocationControllerResourceType = azcorearm.NewResourceType(ProviderNamespace, filepath.Join(ClusterResourceTypeName, SystemAdminCredentialRevocationResourceTypeName, ControllerResourceTypeName))
)

type VersionedResource interface {
}

type VersionedCreatableResource[InternalAPIType any] interface {
	VersionedResource
	NewExternal() any
	ConvertToInternal(existing *InternalAPIType) (*InternalAPIType, error)
}

type VersionedHCPOpenShiftCluster VersionedCreatableResource[HCPOpenShiftCluster]
type VersionedHCPOpenShiftClusterNodePool VersionedCreatableResource[HCPOpenShiftClusterNodePool]
type VersionedHCPOpenShiftClusterExternalAuth VersionedCreatableResource[HCPOpenShiftClusterExternalAuth]
type VersionedHCPOpenShiftVersion VersionedResource

// ValidationPathMapperFunc takes an internal path from validation and converts it to the external path
// for this particular version.  This needs to be as close as possible, but perfection isn't required since fields
// could be split or combined.
type ValidationPathMapperFunc func(path string) string

type Version interface {
	fmt.Stringer

	ValidationPathRewriter(obj any) (ValidationPathMapperFunc, error)

	// Resource Types
	// Passing a nil pointer creates a resource with default values.
	NewHCPOpenShiftCluster(*HCPOpenShiftCluster) VersionedHCPOpenShiftCluster
	NewHCPOpenShiftClusterNodePool(*HCPOpenShiftClusterNodePool) VersionedHCPOpenShiftClusterNodePool
	NewHCPOpenShiftClusterExternalAuth(*HCPOpenShiftClusterExternalAuth) VersionedHCPOpenShiftClusterExternalAuth
	NewHCPOpenShiftVersion(*HCPOpenShiftVersion) VersionedHCPOpenShiftVersion

	// Request Unmarshaling
	UnmarshalHCPOpenShiftClusterAdminCredentialRequest([]byte) (*HCPOpenShiftClusterAdminCredentialRequest, error)

	// Response Marshaling
	MarshalHCPOpenShiftClusterAdminCredential(*HCPOpenShiftClusterAdminCredential) ([]byte, error)
}

// APIRegistry is a way to keep track of versioned interfaces.
// It should always be done per-instance, so we can easily track what registers where and why it does it.
// This construction also gives us a way to unit and integration test different scenarios without impacting a single
// global as we run the tests.
type APIRegistry interface {
	Register(version Version) error
	ListVersions() set.Set[string]
	Lookup(key string) (version Version, ok bool)
}

type apiRegistry struct {
	lock             sync.RWMutex
	versionToDetails map[string]Version
}

func NewAPIRegistry() APIRegistry {
	return &apiRegistry{
		versionToDetails: map[string]Version{},
	}
}

func (a *apiRegistry) Register(version Version) error {
	a.lock.Lock()
	defer a.lock.Unlock()

	if _, exists := a.versionToDetails[version.String()]; exists {
		return fmt.Errorf("version %s already registered", version.String())
	}

	a.versionToDetails[version.String()] = version
	return nil
}

func (a *apiRegistry) ListVersions() set.Set[string] {
	a.lock.RLock()
	defer a.lock.RUnlock()

	return set.KeySet(a.versionToDetails)
}

func (a *apiRegistry) Lookup(key string) (Version, bool) {
	a.lock.RLock()
	defer a.lock.RUnlock()

	version, ok := a.versionToDetails[key]
	return version, ok
}
