package server

import (
	"context"
	"encoding/json"
	"net/http"

	"github.com/gorilla/mux"
	"github.com/pkg/errors"

	"github.com/kopia/kopia/internal/auth"
	"github.com/kopia/kopia/internal/remoterepoapi"
	"github.com/kopia/kopia/internal/serverapi"
	"github.com/kopia/kopia/repo"
	"github.com/kopia/kopia/repo/manifest"
)

func (s *Server) handleManifestGet(ctx context.Context, r *http.Request, body []byte) (interface{}, *apiError) {
	mid := manifest.ID(mux.Vars(r)["manifestID"])

	var data json.RawMessage

	md, err := s.rep.GetManifest(ctx, mid, &data)
	if errors.Is(err, manifest.ErrNotFound) {
		return nil, notFoundError("manifest not found")
	}

	if err != nil {
		return nil, internalServerError(err)
	}

	if !hasManifestAccess(ctx, s, r, md.Labels, auth.AccessLevelRead) {
		return nil, accessDeniedError()
	}

	return &remoterepoapi.ManifestWithMetadata{
		Payload:  data,
		Metadata: md,
	}, nil
}

func (s *Server) handleManifestDelete(ctx context.Context, r *http.Request, body []byte) (interface{}, *apiError) {
	rw, ok := s.rep.(repo.RepositoryWriter)
	if !ok {
		return nil, repositoryNotWritableError()
	}

	mid := manifest.ID(mux.Vars(r)["manifestID"])

	var data json.RawMessage

	em, err := s.rep.GetManifest(ctx, mid, &data)
	if errors.Is(err, manifest.ErrNotFound) {
		return nil, notFoundError("manifest not found")
	}

	if err != nil {
		return nil, internalServerError(err)
	}

	if !hasManifestAccess(ctx, s, r, em.Labels, auth.AccessLevelFull) {
		return nil, accessDeniedError()
	}

	err = rw.DeleteManifest(ctx, mid)
	if errors.Is(err, manifest.ErrNotFound) {
		return nil, notFoundError("manifest not found")
	}

	if err != nil {
		return nil, internalServerError(err)
	}

	return &serverapi.Empty{}, nil
}

func (s *Server) handleManifestList(ctx context.Context, r *http.Request, body []byte) (interface{}, *apiError) {
	// password already validated by a wrapper, no need to check here.
	labels := map[string]string{}

	for k, v := range r.URL.Query() {
		labels[k] = v[0]
	}

	m, err := s.rep.FindManifests(ctx, labels)
	if err != nil {
		return nil, internalServerError(err)
	}

	return filterManifests(m, s.httpAuthorizationInfo(ctx, r)), nil
}

func filterManifests(manifests []*manifest.EntryMetadata, authz auth.AuthorizationInfo) []*manifest.EntryMetadata {
	result := []*manifest.EntryMetadata{}

	for _, m := range manifests {
		if authz.ManifestAccessLevel(m.Labels) >= auth.AccessLevelRead {
			result = append(result, m)
		}
	}

	return result
}

func (s *Server) handleManifestCreate(ctx context.Context, r *http.Request, body []byte) (interface{}, *apiError) {
	rw, ok := s.rep.(repo.RepositoryWriter)
	if !ok {
		return nil, repositoryNotWritableError()
	}

	var req remoterepoapi.ManifestWithMetadata

	if err := json.Unmarshal(body, &req); err != nil {
		return nil, requestError(serverapi.ErrorMalformedRequest, "malformed request")
	}

	if !hasManifestAccess(ctx, s, r, req.Metadata.Labels, auth.AccessLevelAppend) {
		return nil, accessDeniedError()
	}

	id, err := rw.PutManifest(ctx, req.Metadata.Labels, req.Payload)
	if err != nil {
		return nil, internalServerError(err)
	}

	return &manifest.EntryMetadata{ID: id}, nil
}
