// Copyright Project Harbor Authors
//
// 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 replication

import (
	"context"
	"strconv"

	"github.com/goharbor/harbor/src/controller/event"
	repevent "github.com/goharbor/harbor/src/controller/event/handler/replication/event"
	"github.com/goharbor/harbor/src/controller/project"
	"github.com/goharbor/harbor/src/lib/log"
	"github.com/goharbor/harbor/src/lib/orm"
	"github.com/goharbor/harbor/src/pkg/label"
	labmodel "github.com/goharbor/harbor/src/pkg/label/model"
	"github.com/goharbor/harbor/src/pkg/reg/model"
)

// Handler ...
type Handler struct {
}

// Name ...
func (r *Handler) Name() string {
	return "Replication"
}

// Handle ...
func (r *Handler) Handle(ctx context.Context, value interface{}) error {
	pushArtEvent, ok := value.(*event.PushArtifactEvent)
	if ok {
		return r.handlePushArtifact(ctx, pushArtEvent)
	}
	deleteArtEvent, ok := value.(*event.DeleteArtifactEvent)
	if ok {
		return r.handleDeleteArtifact(ctx, deleteArtEvent)
	}
	createTagEvent, ok := value.(*event.CreateTagEvent)
	if ok {
		return r.handleCreateTag(ctx, createTagEvent)
	}
	deleteTagEvent, ok := value.(*event.DeleteTagEvent)
	if ok {
		return r.handleDeleteTag(ctx, deleteTagEvent)
	}
	return nil
}

// IsStateful ...
func (r *Handler) IsStateful() bool {
	return false
}

// abstractLabelNames returns labels name.
func abstractLabelNames(labels []*labmodel.Label) []string {
	res := make([]string, 0, len(labels))
	for _, lab := range labels {
		res = append(res, lab.Name)
	}

	return res
}

func (r *Handler) handlePushArtifact(ctx context.Context, event *event.PushArtifactEvent) error {
	art := event.Artifact
	public := false
	prj, err := project.Ctl.Get(orm.Context(), art.ProjectID, project.Metadata(true))
	if err != nil {
		log.Errorf("failed to get project: %d, error: %v", art.ProjectID, err)
		return err
	}
	public = prj.IsPublic()
	// list attached labels
	labels, err := label.Mgr.ListByArtifact(ctx, art.ID)
	if err != nil {
		log.Errorf("failed to list artifact %d labels, error: %v", art.ID, err)
		return err
	}

	e := &repevent.Event{
		Type: repevent.EventTypeArtifactPush,
		Resource: &model.Resource{
			Type: model.ResourceTypeArtifact,
			Metadata: &model.ResourceMetadata{
				Repository: &model.Repository{
					Name: event.Repository,
					Metadata: map[string]interface{}{
						"public": strconv.FormatBool(public),
					},
				},
				Artifacts: []*model.Artifact{
					{
						Type:   art.Type,
						Digest: art.Digest,
						Tags:   event.Tags,
						Labels: abstractLabelNames(labels),
					}},
			},
		},
		Operator: event.Operator,
	}
	return repevent.Handle(ctx, e)
}

func (r *Handler) handleDeleteArtifact(ctx context.Context, event *event.DeleteArtifactEvent) error {
	art := event.Artifact
	// list attached labels
	labels, err := label.Mgr.ListByArtifact(ctx, art.ID)
	if err != nil {
		log.Errorf("failed to list artifact %d labels, error: %v", art.ID, err)
		return err
	}

	e := &repevent.Event{
		Type: repevent.EventTypeArtifactDelete,
		Resource: &model.Resource{
			Type: model.ResourceTypeArtifact,
			Metadata: &model.ResourceMetadata{
				Repository: &model.Repository{
					Name: event.Repository,
				},
				Artifacts: []*model.Artifact{
					{
						Type:   art.Type,
						Digest: art.Digest,
						Tags:   event.Tags,
						Labels: abstractLabelNames(labels),
					}},
			},
			Deleted: true,
		},
		Operator: event.Operator,
	}
	return repevent.Handle(ctx, e)
}

func (r *Handler) handleCreateTag(ctx context.Context, event *event.CreateTagEvent) error {
	art := event.AttachedArtifact
	public := false
	prj, err := project.Ctl.Get(orm.Context(), art.ProjectID, project.Metadata(true))
	if err != nil {
		log.Errorf("failed to get project: %d, error: %v", art.ProjectID, err)
		return err
	}
	public = prj.IsPublic()
	// list attached labels
	labels, err := label.Mgr.ListByArtifact(ctx, art.ID)
	if err != nil {
		log.Errorf("failed to list artifact %d labels, error: %v", art.ID, err)
		return err
	}

	e := &repevent.Event{
		Type: repevent.EventTypeArtifactPush,
		Resource: &model.Resource{
			Type: model.ResourceTypeArtifact,
			Metadata: &model.ResourceMetadata{
				Repository: &model.Repository{
					Name: event.Repository,
					Metadata: map[string]interface{}{
						"public": strconv.FormatBool(public),
					},
				},
				Artifacts: []*model.Artifact{
					{
						Type:   art.Type,
						Digest: art.Digest,
						Tags:   []string{event.Tag},
						Labels: abstractLabelNames(labels),
					}},
			},
		},
		Operator: event.Operator,
	}
	return repevent.Handle(ctx, e)
}

func (r *Handler) handleDeleteTag(ctx context.Context, event *event.DeleteTagEvent) error {
	art := event.AttachedArtifact
	// list attached labels
	labels, err := label.Mgr.ListByArtifact(ctx, art.ID)
	if err != nil {
		log.Errorf("failed to list artifact %d labels, error: %v", art.ID, err)
		return err
	}

	e := &repevent.Event{
		Type: repevent.EventTypeTagDelete,
		Resource: &model.Resource{
			Type: model.ResourceTypeArtifact,
			Metadata: &model.ResourceMetadata{
				Repository: &model.Repository{
					Name: event.Repository,
				},
				Artifacts: []*model.Artifact{
					{
						Type:   art.Type,
						Digest: art.Digest,
						Tags:   []string{event.Tag},
						Labels: abstractLabelNames(labels),
					}},
			},
			Deleted:     true,
			IsDeleteTag: true,
		},
		Operator: event.Operator,
	}
	return repevent.Handle(ctx, e)
}
