package vulnerability

import (
	"encoding/json"
	"io"
	"time"

	"github.com/anchore/grype/grype/distro"
	grypePkg "github.com/anchore/grype/grype/pkg"
)

// Criteria interfaces are used for FindVulnerabilities calls
type Criteria interface {
	// MatchesVulnerability returns true if the provided value meets the criteria
	MatchesVulnerability(value Vulnerability) (bool, string, error)
}

// MetadataProvider implementations provide ways to look up vulnerability metadata
//
// Deprecated: vulnerability.Vulnerability objects now have metadata included
type MetadataProvider interface {
	// VulnerabilityMetadata returns the metadata associated with a vulnerability
	//
	// Deprecated: vulnerability.Vulnerability objects now have metadata included
	VulnerabilityMetadata(ref Reference) (*Metadata, error)
}

// Provider is the common interface for vulnerability sources to provide searching and metadata, such as a database
type Provider interface {
	PackageSearchNames(grypePkg.Package) []string
	// FindVulnerabilities returns vulnerabilities matching all the provided criteria
	FindVulnerabilities(criteria ...Criteria) ([]Vulnerability, error)

	MetadataProvider

	io.Closer
}

type StoreMetadataProvider interface {
	DataProvenance() (map[string]DataProvenance, error)
}

// EOLChecker is an optional interface that vulnerability providers can implement
// to check the end-of-life status for an operating system. EOL lookups use exact
// matching (no aliasing) since each distro has its own EOL dates.
type EOLChecker interface {
	// GetOperatingSystemEOL returns the EOL date for the given distro.
	// Returns nil dates if no EOL data is available for this distro.
	// Uses exact matching (no aliasing) since distros like CentOS have
	// different EOL dates than their alias targets like RHEL.
	GetOperatingSystemEOL(d *distro.Distro) (eolDate, eoasDate *time.Time, err error)
}

type DataProvenance struct {
	DateCaptured time.Time `json:"captured,omitempty"`
	InputDigest  string    `json:"input,omitempty"`
}

type ProviderStatus struct {
	SchemaVersion string    `json:"schemaVersion"`
	From          string    `json:"from,omitempty"`
	Built         time.Time `json:"built,omitempty"`
	Path          string    `json:"path,omitempty"`
	Error         error     `json:"error,omitempty"`
}

func (s ProviderStatus) MarshalJSON() ([]byte, error) {
	errStr := ""
	if s.Error != nil {
		errStr = s.Error.Error()
	}

	var t string
	if !s.Built.IsZero() {
		t = s.Built.Format(time.RFC3339)
	}

	return json.Marshal(&struct {
		SchemaVersion string `json:"schemaVersion"`
		From          string `json:"from,omitempty"`
		Built         string `json:"built,omitempty"`
		Path          string `json:"path,omitempty"`
		Valid         bool   `json:"valid"`
		Error         string `json:"error,omitempty"`
	}{
		SchemaVersion: s.SchemaVersion,
		From:          s.From,
		Built:         t,
		Path:          s.Path,
		Valid:         s.Error == nil,
		Error:         errStr,
	})
}

func (s DataProvenance) MarshalJSON() ([]byte, error) {
	var t string
	if !s.DateCaptured.IsZero() {
		t = s.DateCaptured.Format(time.RFC3339)
	}

	return json.Marshal(&struct {
		DateCaptured string `json:"captured,omitempty"`
		InputDigest  string `json:"input,omitempty"`
	}{
		DateCaptured: t,
		InputDigest:  s.InputDigest,
	})
}
