// 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/auditext/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"
	TopicCommonEvent       = "COMMON_API"
	ResourceTypeProject    = "project"
	ResourceTypeArtifact   = "artifact"
	ResourceTypeRepository = "repository"
	ResourceTypeRobot      = "robot"
	ResourceTypeTag        = "tag"
)

// 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.AuditLogExt, error) {
	auditLog := &model.AuditLogExt{
		ProjectID:            c.ProjectID,
		OpTime:               c.OccurAt,
		Operation:            rbac.ActionCreate.String(),
		Username:             c.Operator,
		ResourceType:         ResourceTypeProject,
		IsSuccessful:         true,
		OperationDescription: fmt.Sprintf("create project: %s", c.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.AuditLogExt, error) {
	auditLog := &model.AuditLogExt{
		ProjectID:            d.ProjectID,
		OpTime:               d.OccurAt,
		Operation:            rbac.ActionDelete.String(),
		Username:             d.Operator,
		ResourceType:         ResourceTypeProject,
		IsSuccessful:         true,
		OperationDescription: fmt.Sprintf("delete project: %s", d.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.AuditLogExt, error) {
	auditLog := &model.AuditLogExt{
		ProjectID:            d.ProjectID,
		OpTime:               d.OccurAt,
		Operation:            rbac.ActionDelete.String(),
		Username:             d.Operator,
		ResourceType:         ResourceTypeRepository,
		IsSuccessful:         true,
		OperationDescription: fmt.Sprintf("delete repository: %s", d.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
	Labels     []string
	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.AuditLogExt, error) {
	auditLog := &model.AuditLogExt{
		ProjectID:            p.Artifact.ProjectID,
		OpTime:               p.OccurAt,
		Operation:            rbac.ActionCreate.String(),
		Username:             p.Operator,
		IsSuccessful:         true,
		OperationDescription: fmt.Sprintf("push artifact: %s@%s", p.Artifact.RepositoryName, p.Artifact.Digest),
		ResourceType:         ResourceTypeArtifact}

	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.AuditLogExt, error) {
	auditLog := &model.AuditLogExt{
		ProjectID:            p.Artifact.ProjectID,
		OpTime:               p.OccurAt,
		Operation:            rbac.ActionPull.String(),
		Username:             p.Operator,
		IsSuccessful:         true,
		OperationDescription: fmt.Sprintf("pull artifact: %s@%s", p.Artifact.RepositoryName, p.Artifact.Digest),
		ResourceType:         ResourceTypeArtifact}

	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.AuditLogExt, error) {
	auditLog := &model.AuditLogExt{
		ProjectID:            d.Artifact.ProjectID,
		OpTime:               d.OccurAt,
		Operation:            rbac.ActionDelete.String(),
		Username:             d.Operator,
		ResourceType:         ResourceTypeArtifact,
		IsSuccessful:         true,
		OperationDescription: fmt.Sprintf("delete artifact: %s@%s", d.Artifact.RepositoryName, d.Artifact.Digest),
		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
	Labels           []string
	AttachedArtifact *artifact.Artifact
	Operator         string
	OccurAt          time.Time
}

// ResolveToAuditLog ...
func (c *CreateTagEvent) ResolveToAuditLog() (*model.AuditLogExt, error) {
	auditLog := &model.AuditLogExt{
		ProjectID:            c.AttachedArtifact.ProjectID,
		OpTime:               c.OccurAt,
		Operation:            rbac.ActionCreate.String(),
		Username:             c.Operator,
		ResourceType:         ResourceTypeTag,
		IsSuccessful:         true,
		OperationDescription: fmt.Sprintf("create tag: %s:%s", c.Repository, c.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
	Labels           []string
	AttachedArtifact *artifact.Artifact
	Operator         string
	OccurAt          time.Time
}

// ResolveToAuditLog ...
func (d *DeleteTagEvent) ResolveToAuditLog() (*model.AuditLogExt, error) {
	auditLog := &model.AuditLogExt{
		ProjectID:            d.AttachedArtifact.ProjectID,
		OpTime:               d.OccurAt,
		Operation:            rbac.ActionDelete.String(),
		Username:             d.Operator,
		ResourceType:         ResourceTypeTag,
		IsSuccessful:         true,
		OperationDescription: fmt.Sprintf("delete tag: %s:%s", d.Repository, d.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.AuditLogExt, error) {
	auditLog := &model.AuditLogExt{
		ProjectID:            c.Robot.ProjectID,
		OpTime:               c.OccurAt,
		Operation:            rbac.ActionCreate.String(),
		Username:             c.Operator,
		ResourceType:         ResourceTypeRobot,
		IsSuccessful:         true,
		OperationDescription: fmt.Sprintf("create robot: %s", c.Robot.Name),
		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.AuditLogExt, error) {
	auditLog := &model.AuditLogExt{
		ProjectID:            c.Robot.ProjectID,
		OpTime:               c.OccurAt,
		Operation:            rbac.ActionDelete.String(),
		Username:             c.Operator,
		ResourceType:         ResourceTypeRobot,
		IsSuccessful:         true,
		OperationDescription: fmt.Sprintf("delete robot: %s", c.Robot.Name),
		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"))
}
