// 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 event

import (
	"fmt"
	"time"

	"github.com/goharbor/harbor/src/common/rbac"
	"github.com/goharbor/harbor/src/lib/selector"
	"github.com/goharbor/harbor/src/pkg/artifact"
	"github.com/goharbor/harbor/src/pkg/audit/model"
	proModels "github.com/goharbor/harbor/src/pkg/project/models"
	robotModel "github.com/goharbor/harbor/src/pkg/robot/model"
	v1 "github.com/goharbor/harbor/src/pkg/scan/rest/v1"
)

// the event consumers can refer to this file to find all topics and the corresponding event structures

// const definition
const (
	TopicCreateProject     = "CREATE_PROJECT"
	TopicDeleteProject     = "DELETE_PROJECT"
	TopicPushArtifact      = "PUSH_ARTIFACT"
	TopicPullArtifact      = "PULL_ARTIFACT"
	TopicDeleteArtifact    = "DELETE_ARTIFACT"
	TopicDeleteRepository  = "DELETE_REPOSITORY"
	TopicCreateTag         = "CREATE_TAG"
	TopicDeleteTag         = "DELETE_TAG"
	TopicScanningFailed    = "SCANNING_FAILED"
	TopicScanningStopped   = "SCANNING_STOPPED"
	TopicScanningCompleted = "SCANNING_COMPLETED"
	// QuotaExceedTopic is topic for quota warning event, the usage reaches the warning bar of limitation, like 85%
	TopicQuotaWarning    = "QUOTA_WARNING"
	TopicQuotaExceed     = "QUOTA_EXCEED"
	TopicReplication     = "REPLICATION"
	TopicArtifactLabeled = "ARTIFACT_LABELED"
	TopicTagRetention    = "TAG_RETENTION"
	TopicCreateRobot     = "CREATE_ROBOT"
	TopicDeleteRobot     = "DELETE_ROBOT"
)

// CreateProjectEvent is the creating project event
type CreateProjectEvent struct {
	EventType string
	ProjectID int64
	Project   string
	Operator  string
	OccurAt   time.Time
}

// ResolveToAuditLog ...
func (c *CreateProjectEvent) ResolveToAuditLog() (*model.AuditLog, error) {
	auditLog := &model.AuditLog{
		ProjectID:    c.ProjectID,
		OpTime:       c.OccurAt,
		Operation:    rbac.ActionCreate.String(),
		Username:     c.Operator,
		ResourceType: "project",
		Resource:     c.Project}
	return auditLog, nil
}

func (c *CreateProjectEvent) String() string {
	return fmt.Sprintf("ID-%d Name-%s Operator-%s OccurAt-%s",
		c.ProjectID, c.Project, c.Operator, c.OccurAt.Format("2006-01-02 15:04:05"))
}

// DeleteProjectEvent is the deleting project event
type DeleteProjectEvent struct {
	EventType string
	ProjectID int64
	Project   string
	Operator  string
	OccurAt   time.Time
}

// ResolveToAuditLog ...
func (d *DeleteProjectEvent) ResolveToAuditLog() (*model.AuditLog, error) {
	auditLog := &model.AuditLog{
		ProjectID:    d.ProjectID,
		OpTime:       d.OccurAt,
		Operation:    rbac.ActionDelete.String(),
		Username:     d.Operator,
		ResourceType: "project",
		Resource:     d.Project}
	return auditLog, nil
}

func (d *DeleteProjectEvent) String() string {
	return fmt.Sprintf("ID-%d Name-%s Operator-%s OccurAt-%s",
		d.ProjectID, d.Project, d.Operator, d.OccurAt.Format("2006-01-02 15:04:05"))
}

// DeleteRepositoryEvent is the deleting repository event
type DeleteRepositoryEvent struct {
	EventType  string
	ProjectID  int64
	Repository string
	Operator   string
	OccurAt    time.Time
}

// ResolveToAuditLog ...
func (d *DeleteRepositoryEvent) ResolveToAuditLog() (*model.AuditLog, error) {
	auditLog := &model.AuditLog{
		ProjectID:    d.ProjectID,
		OpTime:       d.OccurAt,
		Operation:    rbac.ActionDelete.String(),
		Username:     d.Operator,
		ResourceType: "repository",
		Resource:     d.Repository,
	}
	return auditLog, nil
}

func (d *DeleteRepositoryEvent) String() string {
	return fmt.Sprintf("ID-%d Repository-%s Operator-%s OccurAt-%s",
		d.ProjectID, d.Repository, d.Operator, d.OccurAt.Format("2006-01-02 15:04:05"))
}

// ArtifactEvent is the pushing/pulling artifact event
type ArtifactEvent struct {
	EventType  string
	Repository string
	Artifact   *artifact.Artifact
	Tags       []string // when the artifact is pushed by digest, the tag here will be null
	Operator   string
	OccurAt    time.Time
}

func (a *ArtifactEvent) String() string {
	return fmt.Sprintf("ID-%d, Repository-%s Tags-%s Digest-%s Operator-%s OccurAt-%s",
		a.Artifact.ID, a.Repository, a.Tags, a.Artifact.Digest, a.Operator,
		a.OccurAt.Format("2006-01-02 15:04:05"))
}

// PushArtifactEvent is the pushing artifact event
type PushArtifactEvent struct {
	*ArtifactEvent
}

// ResolveToAuditLog ...
func (p *PushArtifactEvent) ResolveToAuditLog() (*model.AuditLog, error) {
	auditLog := &model.AuditLog{
		ProjectID:    p.Artifact.ProjectID,
		OpTime:       p.OccurAt,
		Operation:    rbac.ActionCreate.String(),
		Username:     p.Operator,
		ResourceType: "artifact"}

	if len(p.Tags) == 0 {
		auditLog.Resource = fmt.Sprintf("%s@%s",
			p.Artifact.RepositoryName, p.Artifact.Digest)
	} else {
		auditLog.Resource = fmt.Sprintf("%s:%s",
			p.Artifact.RepositoryName, p.Tags[0])
	}

	return auditLog, nil
}

func (p *PushArtifactEvent) String() string {
	return p.ArtifactEvent.String()
}

// PullArtifactEvent is the pulling artifact event
type PullArtifactEvent struct {
	*ArtifactEvent
}

// ResolveToAuditLog ...
func (p *PullArtifactEvent) ResolveToAuditLog() (*model.AuditLog, error) {
	auditLog := &model.AuditLog{
		ProjectID:    p.Artifact.ProjectID,
		OpTime:       p.OccurAt,
		Operation:    rbac.ActionPull.String(),
		Username:     p.Operator,
		ResourceType: "artifact"}

	if len(p.Tags) == 0 {
		auditLog.Resource = fmt.Sprintf("%s@%s",
			p.Artifact.RepositoryName, p.Artifact.Digest)
	} else {
		auditLog.Resource = fmt.Sprintf("%s:%s",
			p.Artifact.RepositoryName, p.Tags[0])
	}

	// for pull public resource
	if p.Operator == "" {
		auditLog.Username = "anonymous"
	} else {
		auditLog.Username = p.Operator
	}

	return auditLog, nil
}

func (p *PullArtifactEvent) String() string {
	return p.ArtifactEvent.String()
}

// DeleteArtifactEvent is the deleting artifact event
type DeleteArtifactEvent struct {
	*ArtifactEvent
}

// ResolveToAuditLog ...
func (d *DeleteArtifactEvent) ResolveToAuditLog() (*model.AuditLog, error) {
	auditLog := &model.AuditLog{
		ProjectID:    d.Artifact.ProjectID,
		OpTime:       d.OccurAt,
		Operation:    rbac.ActionDelete.String(),
		Username:     d.Operator,
		ResourceType: "artifact",
		Resource:     fmt.Sprintf("%s@%s", d.Artifact.RepositoryName, d.Artifact.Digest)}
	return auditLog, nil
}

func (d *DeleteArtifactEvent) String() string {
	return d.ArtifactEvent.String()
}

// CreateTagEvent is the creating tag event
type CreateTagEvent struct {
	EventType        string
	Repository       string
	Tag              string
	AttachedArtifact *artifact.Artifact
	Operator         string
	OccurAt          time.Time
}

// ResolveToAuditLog ...
func (c *CreateTagEvent) ResolveToAuditLog() (*model.AuditLog, error) {
	auditLog := &model.AuditLog{
		ProjectID:    c.AttachedArtifact.ProjectID,
		OpTime:       c.OccurAt,
		Operation:    rbac.ActionCreate.String(),
		Username:     c.Operator,
		ResourceType: "tag",
		Resource:     fmt.Sprintf("%s:%s", c.Repository, c.Tag)}
	return auditLog, nil
}

func (c *CreateTagEvent) String() string {
	return fmt.Sprintf("ArtifactID-%d, Repository-%s Tag-%s Digest-%s Operator-%s OccurAt-%s",
		c.AttachedArtifact.ID, c.Repository, c.Tag, c.AttachedArtifact.Digest, c.Operator,
		c.OccurAt.Format("2006-01-02 15:04:05"))
}

// DeleteTagEvent is the deleting tag event
type DeleteTagEvent struct {
	EventType        string
	Repository       string
	Tag              string
	AttachedArtifact *artifact.Artifact
	Operator         string
	OccurAt          time.Time
}

// ResolveToAuditLog ...
func (d *DeleteTagEvent) ResolveToAuditLog() (*model.AuditLog, error) {
	auditLog := &model.AuditLog{
		ProjectID:    d.AttachedArtifact.ProjectID,
		OpTime:       d.OccurAt,
		Operation:    rbac.ActionDelete.String(),
		Username:     d.Operator,
		ResourceType: "tag",
		Resource:     fmt.Sprintf("%s:%s", d.Repository, d.Tag)}
	return auditLog, nil
}

func (d *DeleteTagEvent) String() string {
	return fmt.Sprintf("ArtifactID-%d, Repository-%s Tag-%s Digest-%s Operator-%s OccurAt-%s",
		d.AttachedArtifact.ID, d.Repository, d.Tag, d.AttachedArtifact.Digest, d.Operator,
		d.OccurAt.Format("2006-01-02 15:04:05"))
}

// ScanImageEvent is scanning image related event data to publish
type ScanImageEvent struct {
	EventType string
	ScanType  string
	Artifact  *v1.Artifact
	OccurAt   time.Time
	Operator  string
}

func (s *ScanImageEvent) String() string {
	return fmt.Sprintf("Artifact-%+v Operator-%s OccurAt-%s",
		s.Artifact, s.Operator, s.OccurAt.Format("2006-01-02 15:04:05"))
}

// QuotaEvent is project quota related event data to publish
type QuotaEvent struct {
	EventType string
	Project   *proModels.Project
	Resource  *ImgResource
	OccurAt   time.Time
	RepoName  string
	Msg       string
	Operator  string
}

func (q *QuotaEvent) String() string {
	return fmt.Sprintf("ProjectID-%d RepoName-%s Resource-%+v Msg-%s OccurAt-%s",
		q.Project.ProjectID, q.RepoName, q.Resource, q.Msg, q.OccurAt.Format("2006-01-02 15:04:05"))
}

// ImgResource include image digest and tag
type ImgResource struct {
	Digest string
	Tag    string
}

// ReplicationEvent is replication related event data to publish
type ReplicationEvent struct {
	EventType         string
	ReplicationTaskID int64
	OccurAt           time.Time
	Status            string
}

func (r *ReplicationEvent) String() string {
	return fmt.Sprintf("ReplicationTaskID-%d Status-%s OccurAt-%s",
		r.ReplicationTaskID, r.Status, r.OccurAt.Format("2006-01-02 15:04:05"))
}

// ArtifactLabeledEvent is event data of artifact labeled
type ArtifactLabeledEvent struct {
	ArtifactID int64
	LabelID    int64
	OccurAt    time.Time
	Operator   string
}

func (al *ArtifactLabeledEvent) String() string {
	return fmt.Sprintf("ArtifactID-%d LabelID-%d Operator-%s OccurAt-%s",
		al.ArtifactID, al.LabelID, al.Operator, al.OccurAt.Format("2006-01-02 15:04:05"))
}

// RetentionEvent is tag retention related event data to publish
type RetentionEvent struct {
	TaskID    int64
	EventType string
	OccurAt   time.Time
	Status    string
	Deleted   []*selector.Result
	Total     int
	Retained  int
}

func (r *RetentionEvent) String() string {
	candidates := []string{}
	for _, candidate := range r.Deleted {
		candidates = append(candidates, fmt.Sprintf("%s:%s:%s", candidate.Target.Namespace,
			candidate.Target.Repository, candidate.Target.Tags))
	}

	return fmt.Sprintf("TaskID-%d Status-%s Deleted-%s OccurAt-%s",
		r.TaskID, r.Status, candidates, r.OccurAt.Format("2006-01-02 15:04:05"))
}

// CreateRobotEvent is the creating robot event
type CreateRobotEvent struct {
	EventType string
	Robot     *robotModel.Robot
	Operator  string
	OccurAt   time.Time
}

// ResolveToAuditLog ...
func (c *CreateRobotEvent) ResolveToAuditLog() (*model.AuditLog, error) {
	auditLog := &model.AuditLog{
		ProjectID:    c.Robot.ProjectID,
		OpTime:       c.OccurAt,
		Operation:    rbac.ActionCreate.String(),
		Username:     c.Operator,
		ResourceType: "robot",
		Resource:     c.Robot.Name}
	return auditLog, nil
}

func (c *CreateRobotEvent) String() string {
	return fmt.Sprintf("Name-%s Operator-%s OccurAt-%s",
		c.Robot.Name, c.Operator, c.OccurAt.Format("2006-01-02 15:04:05"))
}

// DeleteRobotEvent is the deleting robot event
type DeleteRobotEvent struct {
	EventType string
	Robot     *robotModel.Robot
	Operator  string
	OccurAt   time.Time
}

// ResolveToAuditLog ...
func (c *DeleteRobotEvent) ResolveToAuditLog() (*model.AuditLog, error) {
	auditLog := &model.AuditLog{
		ProjectID:    c.Robot.ProjectID,
		OpTime:       c.OccurAt,
		Operation:    rbac.ActionDelete.String(),
		Username:     c.Operator,
		ResourceType: "robot",
		Resource:     c.Robot.Name}
	return auditLog, nil
}

func (c *DeleteRobotEvent) String() string {
	return fmt.Sprintf("Name-%s Operator-%s OccurAt-%s",
		c.Robot.Name, c.Operator, c.OccurAt.Format("2006-01-02 15:04:05"))
}
