// 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/compliance/v1alpha1"
)

// NewComplianceClient returns a new typed resource client for Compliance instances
func NewComplianceClient(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("Compliance"), v1alpha1.ComplianceResourcePlural)

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

// NewComplianceSyncher creates a new Compliance synchronization handler
func NewComplianceSyncher(hubClusterConfig, managedClusterConfig *rest.Config) (*ComplianceSyncher, error) {
	cs := &ComplianceSyncher{}
	var err error

	if cs.hub, err = NewComplianceClient(hubClusterConfig); err != nil {
		return nil, err
	}
	if cs.managed, err = NewComplianceClient(managedClusterConfig); err != nil {
		return nil, err
	}
	cs.inplace = sameAPIEndpopint(hubClusterConfig, managedClusterConfig)
	// @todo consider source and destination namespace names as well (once supported)
	return cs, nil
}

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

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

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

// @todo the below functions can be removed by refactoring them as function closures in Watch* methods

// propagate specification changes from remote to local.
func (aps *ComplianceSyncher) hubReconcile(ctx context.Context, rObj *v1alpha1.Compliance) 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 *ComplianceSyncher) hubFinalize(ctx context.Context, rObj *v1alpha1.Compliance) error {
	if aps.inplace {
		return nil
	}
	return specFinalize(ctx, rObj, aps.hub, aps.managed)
}

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

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