package server

import (
	"context"

	google_protobuf "github.com/golang/protobuf/ptypes/empty"
	"github.com/google/wire"
	"golang.org/x/xerrors"

	"github.com/aquasecurity/fanal/cache"
	ftypes "github.com/aquasecurity/fanal/types"
	"github.com/aquasecurity/trivy/pkg/rpc"
	"github.com/aquasecurity/trivy/pkg/scanner"
	"github.com/aquasecurity/trivy/pkg/scanner/local"
	"github.com/aquasecurity/trivy/pkg/types"
	"github.com/aquasecurity/trivy/pkg/vulnerability"
	rpcCache "github.com/aquasecurity/trivy/rpc/cache"
	rpcScanner "github.com/aquasecurity/trivy/rpc/scanner"
)

var ScanSuperSet = wire.NewSet(
	local.SuperSet,
	wire.Bind(new(scanner.Driver), new(local.Scanner)),
	vulnerability.SuperSet,
	NewScanServer,
)

type ScanServer struct {
	localScanner scanner.Driver
	vulnClient   vulnerability.Operation
}

func NewScanServer(s scanner.Driver, vulnClient vulnerability.Operation) *ScanServer {
	return &ScanServer{localScanner: s, vulnClient: vulnClient}
}

func (s *ScanServer) Scan(_ context.Context, in *rpcScanner.ScanRequest) (*rpcScanner.ScanResponse, error) {
	options := types.ScanOptions{VulnType: in.Options.VulnType}
	results, os, eosl, err := s.localScanner.Scan(in.Target, in.ImageId, in.LayerIds, options)
	if err != nil {
		return nil, xerrors.Errorf("failed scan, %s: %w", in.Target, err)
	}

	for i := range results {
		s.vulnClient.FillInfo(results[i].Vulnerabilities, results[i].Type)
	}
	return rpc.ConvertToRpcScanResponse(results, os, eosl), nil
}

type CacheServer struct {
	cache cache.Cache
}

func NewCacheServer(c cache.Cache) *CacheServer {
	return &CacheServer{cache: c}
}

func (s *CacheServer) PutImage(_ context.Context, in *rpcCache.PutImageRequest) (*google_protobuf.Empty, error) {
	if in.ImageInfo == nil {
		return nil, xerrors.Errorf("empty image info")
	}
	imageInfo := rpc.ConvertFromRpcPutImageRequest(in)
	if err := s.cache.PutImage(in.ImageId, imageInfo); err != nil {
		return nil, xerrors.Errorf("unable to store image info in cache: %w", err)
	}
	return &google_protobuf.Empty{}, nil
}

func (s *CacheServer) PutLayer(_ context.Context, in *rpcCache.PutLayerRequest) (*google_protobuf.Empty, error) {
	if in.LayerInfo == nil {
		return nil, xerrors.Errorf("empty layer info")
	}
	layerInfo := rpc.ConvertFromRpcPutLayerRequest(in)
	if err := s.cache.PutLayer(in.DiffId, layerInfo); err != nil {
		return nil, xerrors.Errorf("unable to store layer info in cache: %w", err)
	}
	return &google_protobuf.Empty{}, nil
}

func (s *CacheServer) MissingLayers(_ context.Context, in *rpcCache.MissingLayersRequest) (*rpcCache.MissingLayersResponse, error) {
	var layerIDs []string
	for _, layerID := range in.LayerIds {
		l, err := s.cache.GetLayer(layerID)
		if err != nil || l.SchemaVersion != ftypes.LayerJSONSchemaVersion {
			layerIDs = append(layerIDs, layerID)
		}
	}
	var missingImage bool
	img, err := s.cache.GetImage(in.ImageId)
	if err != nil || img.SchemaVersion != ftypes.ImageJSONSchemaVersion {
		missingImage = true
	}
	return &rpcCache.MissingLayersResponse{MissingImage: missingImage, MissingLayerIds: layerIDs}, nil
}
