// Licensed Materials - Property of IBM
// (c) Copyright IBM Corporation 2018, 2019. All Rights Reserved.
// Note to U.S. Government Users Restricted Rights:
// Use, duplication or disclosure restricted by GSA ADP Schedule
// Contract with IBM Corp.

package sync

import (
	"errors"

	"k8s.io/client-go/rest"

	seedclient "github.com/open-cluster-management/seed-sdk/pkg/client"
	"github.com/open-cluster-management/seed-sdk/pkg/context"
	seedcontroller "github.com/open-cluster-management/seed-sdk/pkg/controller"
	"github.com/open-cluster-management/seed-sdk/pkg/scheme"

	"github.com/open-cluster-management/hcm-compliance/pkg/apis/accesspolicy/v1alpha1"
)

// NewAccessPolicyClient returns a new typed resource client for AccessPolicy instances
func NewAccessPolicyClient(cfg *rest.Config) (*seedclient.ResourceClient, error) {
	if cfg == nil {
		return nil, errors.New("nil config for NewComplianceClient()")
	}

	ks := scheme.NewScheme()
	v1alpha1.AddToScheme(ks.Scheme)
	ks.SetPlural(v1alpha1.SchemeGroupVersion.WithKind("AccessPolicy"), v1alpha1.AccessPolicyResourcePlural)

	return seedclient.New(cfg, v1alpha1.SchemeGroupVersion, ks)
}

// NewAccessPolicySyncher creates a new AccessPolicy synchronization handler
func NewAccessPolicySyncher(hubClusterConfig, managedClusterConfig *rest.Config) (*AccessPolicySyncher, error) {
	acs := &AccessPolicySyncher{}
	var err error

	if acs.hub, err = NewAccessPolicyClient(hubClusterConfig); err != nil {
		return nil, err
	}
	if acs.managed, err = NewAccessPolicyClient(managedClusterConfig); err != nil {
		return nil, err
	}
	acs.inplace = sameAPIEndpopint(hubClusterConfig, managedClusterConfig) // @todo account for source and destination namespaces
	return acs, nil
}

// WatchSpecEvents watches for AccessPolicy.Spec events and installs handlers for them
func (aps *AccessPolicySyncher) WatchSpecEvents(cb seedcontroller.Builder) seedcontroller.WatchBuilder {
	return cb.Watch("accesspolicies.v1alpha1.accesspolicy.mcm.ibm.com", "AccessPolicy", &v1alpha1.SchemeBuilder).
		Reconcile(aps.hubReconcile).
		Finalize(aps.hubFinalize)
}

// WatchStatusEvents watches for AccessPolicy.Status events and installs handlers for them
func (aps *AccessPolicySyncher) WatchStatusEvents(cb seedcontroller.Builder) seedcontroller.WatchBuilder {
	return cb.Watch("accesspolicies.v1alpha1.accesspolicy.mcm.ibm.com", "AccessPolicy", &v1alpha1.SchemeBuilder).
		Reconcile(aps.managedReconcile).
		Finalize(aps.managedFinalize)
}

// AccessPolicySyncher encapsulates objects needed for synchronizing AccessPolicy instances
type AccessPolicySyncher struct {
	inplace bool
	hub     *seedclient.ResourceClient
	managed *seedclient.ResourceClient
}

// propagate specification changes from remote to local.
func (aps *AccessPolicySyncher) hubReconcile(ctx context.Context, rObj *v1alpha1.AccessPolicy) error {
	if aps.inplace {
		return nil
	}
	return specReconcile(ctx, rObj, aps.hub, aps.managed)
}

// handle propagation of deletion from remote to local object.
func (aps *AccessPolicySyncher) hubFinalize(ctx context.Context, rObj *v1alpha1.AccessPolicy) error {
	if aps.inplace {
		return nil
	}
	return specFinalize(ctx, rObj, aps.hub, aps.managed)
}

// propagate status changes on local to remote.
func (aps *AccessPolicySyncher) managedReconcile(ctx context.Context, lObj *v1alpha1.AccessPolicy) error {
	if aps.inplace {
		return nil
	}
	return statusReconcile(ctx, lObj, aps.hub, aps.managed)
}

// handle deletion of local (managed) instances
func (aps *AccessPolicySyncher) managedFinalize(ctx context.Context, lObj *v1alpha1.AccessPolicy) error {
	if aps.inplace {
		return nil
	}
	return statusFinalize(ctx, lObj, aps.hub, aps.managed)
}
