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

import (
	"context"
	"time"

	"github.com/goharbor/harbor/src/lib/q"
	"github.com/goharbor/harbor/src/pkg/artifact/dao"
)

// Manager is the only interface of artifact module to provide the management functions for artifacts
type Manager interface {
	// Count returns the total count of artifacts according to the query.
	// The artifacts that referenced by others and without tags are not counted
	Count(ctx context.Context, query *q.Query) (total int64, err error)
	// List artifacts according to the query. The artifacts that referenced by others and
	// without tags are not returned
	List(ctx context.Context, query *q.Query) (artifacts []*Artifact, err error)
	// Get the artifact specified by the ID
	Get(ctx context.Context, id int64) (artifact *Artifact, err error)
	// GetByDigest returns the artifact specified by repository and digest
	GetByDigest(ctx context.Context, repository, digest string) (artifact *Artifact, err error)
	// Create the artifact. If the artifact is an index, make sure all the artifacts it references
	// already exist
	Create(ctx context.Context, artifact *Artifact) (id int64, err error)
	// Delete just deletes the artifact record. The underlying data of registry will be
	// removed during garbage collection
	Delete(ctx context.Context, id int64) (err error)
	// Update the artifact. Only the properties specified by "props" will be updated if it is set
	Update(ctx context.Context, artifact *Artifact, props ...string) (err error)
	// UpdatePullTime updates artifact pull time by ID.
	UpdatePullTime(ctx context.Context, id int64, pullTime time.Time) (err error)
	// ListReferences according to the query
	ListReferences(ctx context.Context, query *q.Query) (references []*Reference, err error)
	// DeleteReference specified by ID
	DeleteReference(ctx context.Context, id int64) (err error)
	// ListWithLatest list the artifacts when the latest_in_repository in the query was set
	ListWithLatest(ctx context.Context, query *q.Query) (artifacts []*Artifact, err error)
}

// NewManager returns an instance of the default manager
func NewManager() Manager {
	return &manager{
		dao.New(),
	}
}

var _ Manager = &manager{}

type manager struct {
	dao dao.DAO
}

func (m *manager) Count(ctx context.Context, query *q.Query) (int64, error) {
	return m.dao.Count(ctx, query)
}

func (m *manager) List(ctx context.Context, query *q.Query) ([]*Artifact, error) {
	arts, err := m.dao.List(ctx, query)
	if err != nil {
		return nil, err
	}
	var artifacts []*Artifact
	for _, art := range arts {
		artifact, err := m.assemble(ctx, art)
		if err != nil {
			return nil, err
		}
		artifacts = append(artifacts, artifact)
	}
	return artifacts, nil
}

func (m *manager) Get(ctx context.Context, id int64) (*Artifact, error) {
	art, err := m.dao.Get(ctx, id)
	if err != nil {
		return nil, err
	}
	return m.assemble(ctx, art)
}

func (m *manager) GetByDigest(ctx context.Context, repository, digest string) (*Artifact, error) {
	art, err := m.dao.GetByDigest(ctx, repository, digest)
	if err != nil {
		return nil, err
	}
	return m.assemble(ctx, art)
}

func (m *manager) Create(ctx context.Context, artifact *Artifact) (int64, error) {
	id, err := m.dao.Create(ctx, artifact.To())
	if err != nil {
		return 0, err
	}
	for _, reference := range artifact.References {
		reference.ParentID = id
		if _, err = m.dao.CreateReference(ctx, reference.To()); err != nil {
			return 0, err
		}
	}
	return id, nil
}
func (m *manager) Delete(ctx context.Context, id int64) error {
	// delete references
	if err := m.dao.DeleteReferences(ctx, id); err != nil {
		return err
	}
	// delete artifact
	return m.dao.Delete(ctx, id)
}

func (m *manager) Update(ctx context.Context, artifact *Artifact, props ...string) (err error) {
	return m.dao.Update(ctx, artifact.To(), props...)
}

func (m *manager) UpdatePullTime(ctx context.Context, id int64, pullTime time.Time) (err error) {
	return m.dao.UpdatePullTime(ctx, id, pullTime)
}

func (m *manager) ListReferences(ctx context.Context, query *q.Query) ([]*Reference, error) {
	references, err := m.dao.ListReferences(ctx, query)
	if err != nil {
		return nil, err
	}
	var refs []*Reference
	for _, reference := range references {
		ref := &Reference{}
		ref.From(reference)
		refs = append(refs, ref)
	}
	return refs, nil
}

func (m *manager) DeleteReference(ctx context.Context, id int64) error {
	return m.dao.DeleteReference(ctx, id)
}

func (m *manager) ListWithLatest(ctx context.Context, query *q.Query) ([]*Artifact, error) {
	arts, err := m.dao.ListWithLatest(ctx, query)
	if err != nil {
		return nil, err
	}
	var artifacts []*Artifact
	for _, art := range arts {
		artifact, err := m.assemble(ctx, art)
		if err != nil {
			return nil, err
		}
		artifacts = append(artifacts, artifact)
	}
	return artifacts, nil
}

// assemble the artifact with references populated
func (m *manager) assemble(ctx context.Context, art *dao.Artifact) (*Artifact, error) {
	artifact := &Artifact{}
	// convert from database object
	artifact.From(art)

	// populate the references
	if artifact.IsImageIndex() {
		references, err := m.ListReferences(ctx, q.New(q.KeyWords{"ParentID": artifact.ID}))
		if err != nil {
			return nil, err
		}
		artifact.References = references
	}

	return artifact, nil
}
