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

import (
	"context"
	"encoding/json"
	"regexp"
	"strings"

	"github.com/docker/distribution/manifest/schema2"
	// annotation parsers will be registered
	"github.com/goharbor/harbor/src/controller/artifact/annotation"
	"github.com/goharbor/harbor/src/lib/errors"
	"github.com/goharbor/harbor/src/lib/log"
	"github.com/goharbor/harbor/src/pkg/artifact"
	"github.com/goharbor/harbor/src/pkg/registry"
	v1 "github.com/opencontainers/image-spec/specs-go/v1"
)

const (
	// ArtifactTypeUnknown defines the type for the unknown artifacts
	ArtifactTypeUnknown = "UNKNOWN"
)

var (
	// DefaultProcessor is to process artifact which has no specific processor
	DefaultProcessor = &defaultProcessor{regCli: registry.Cli}

	artifactTypeRegExp = regexp.MustCompile(`^application/vnd\.[^.]*\.(.*)\.config\.[^.]*\+json$`)
)

// the default processor to process artifact
type defaultProcessor struct {
	regCli registry.Client
}

func (d *defaultProcessor) GetArtifactType(ctx context.Context, artifact *artifact.Artifact) string {
	// try to parse the type from the media type
	strs := artifactTypeRegExp.FindStringSubmatch(artifact.MediaType)
	if len(strs) == 2 {
		return strings.ToUpper(strs[1])
	}
	// can not get the artifact type from the media type, return unknown
	return ArtifactTypeUnknown
}
func (d *defaultProcessor) ListAdditionTypes(ctx context.Context, artifact *artifact.Artifact) []string {
	return nil
}

// The default processor will process user-defined artifact.
// AbstractMetadata will abstract data in a specific way.
// Annotation keys in artifact annotation will decide which content will be processed in artifact.
// Here is a manifest example:
// {
//   "schemaVersion": 2,
//   "config": {
//       "mediaType": "application/vnd.caicloud.model.config.v1alpha1+json",
//       "digest": "sha256:be948daf0e22f264ea70b713ea0db35050ae659c185706aa2fad74834455fe8c",
//       "size": 187,
//       "annotations": {
//           "io.goharbor.artifact.v1alpha1.skip-list": "metrics,git"
//       }
//   },
//   "layers": [
//       {
//           "mediaType": "image/png",
//           "digest": "sha256:d923b93eadde0af5c639a972710a4d919066aba5d0dfbf4b9385099f70272da0",
//           "size": 166015,
//           "annotations": {
//               "io.goharbor.artifact.v1alpha1.icon": ""
//           }
//       },
//       {
//           "mediaType": "application/tar+gzip",
//           "digest": "sha256:d923b93eadde0af5c639a972710a4d919066aba5d0dfbf4b9385099f70272da0",
//           "size": 166015
//       }
//   ]
// }
func (d *defaultProcessor) AbstractMetadata(ctx context.Context, artifact *artifact.Artifact, manifest []byte) error {
	if artifact.ManifestMediaType != v1.MediaTypeImageManifest && artifact.ManifestMediaType != schema2.MediaTypeManifest {
		return nil
	}
	// get manifest
	mani := &v1.Manifest{}
	if err := json.Unmarshal(manifest, mani); err != nil {
		return err
	}
	// get config layer
	_, blob, err := d.regCli.PullBlob(artifact.RepositoryName, mani.Config.Digest.String())
	if err != nil {
		return err
	}
	defer blob.Close()
	// parse metadata from config layer
	metadata := map[string]interface{}{}
	// Some artifact may not have empty config layer.
	if mani.Config.Size != 0 {
		if err := json.NewDecoder(blob).Decode(&metadata); err != nil {
			return err
		}
	}
	// Populate all metadata into the ExtraAttrs first.
	artifact.ExtraAttrs = metadata
	annotationParser := annotation.NewParser()
	err = annotationParser.Parse(ctx, artifact, manifest)
	if err != nil {
		log.Errorf("the annotation parser parse annotation for artifact error: %v", err)
	}

	return nil
}

func (d *defaultProcessor) AbstractAddition(ctx context.Context, artifact *artifact.Artifact, addition string) (*Addition, error) {
	// Addition not support for user-defined artifact yet.
	// It will be support in the future.
	// return error directly
	return nil, errors.New(nil).WithCode(errors.BadRequestCode).
		WithMessage("the processor for artifact %s not found, cannot get the addition", artifact.Type)
}
