package v4

import (
	"encoding/json"
)

// VulnerabilityMatchExclusion represents the minimum data fields necessary to automatically filter certain
// vulnerabilities from match results based on the specified constraints.
type VulnerabilityMatchExclusion struct {
	ID            string                                  `json:"id"`                    // The identifier of the vulnerability or advisory
	Constraints   []VulnerabilityMatchExclusionConstraint `json:"constraints,omitempty"` // The constraints under which the exclusion applies
	Justification string                                  `json:"justification"`         // Justification for the exclusion
}

// VulnerabilityMatchExclusionConstraint describes criteria for which matches should be excluded
type VulnerabilityMatchExclusionConstraint struct {
	Vulnerability VulnerabilityExclusionConstraint `json:"vulnerability,omitempty"` // Vulnerability exclusion criteria
	Package       PackageExclusionConstraint       `json:"package,omitempty"`       // Package exclusion criteria
	ExtraFields   map[string]interface{}           `json:"-"`
}

func (c VulnerabilityMatchExclusionConstraint) Usable() bool {
	return len(c.ExtraFields) == 0 && c.Vulnerability.Usable() && c.Package.Usable()
}

func (c *VulnerabilityMatchExclusionConstraint) UnmarshalJSON(data []byte) error {
	// Create a new type from the target type to avoid recursion.
	type _vulnerabilityMatchExclusionConstraint VulnerabilityMatchExclusionConstraint

	// Unmarshal into an instance of the new type.
	var _c _vulnerabilityMatchExclusionConstraint
	if err := json.Unmarshal(data, &_c); err != nil {
		return err
	}

	if err := json.Unmarshal(data, &_c.ExtraFields); err != nil {
		return err
	}

	delete(_c.ExtraFields, "vulnerability")
	delete(_c.ExtraFields, "package")

	if len(_c.ExtraFields) == 0 {
		_c.ExtraFields = nil
	}

	// Cast the new type instance to the original type and assign.
	*c = VulnerabilityMatchExclusionConstraint(_c)
	return nil
}

// VulnerabilityExclusionConstraint describes criteria for excluding a match based on additional vulnerability components
type VulnerabilityExclusionConstraint struct {
	Namespace   string                 `json:"namespace,omitempty"` // Vulnerability namespace
	FixState    FixState               `json:"fix_state,omitempty"` // Vulnerability fix state
	ExtraFields map[string]interface{} `json:"-"`
}

func (v VulnerabilityExclusionConstraint) Usable() bool {
	return len(v.ExtraFields) == 0
}

func (v *VulnerabilityExclusionConstraint) UnmarshalJSON(data []byte) error {
	// Create a new type from the target type to avoid recursion.
	type _vulnerabilityExclusionConstraint VulnerabilityExclusionConstraint

	// Unmarshal into an instance of the new type.
	var _v _vulnerabilityExclusionConstraint
	if err := json.Unmarshal(data, &_v); err != nil {
		return err
	}

	if err := json.Unmarshal(data, &_v.ExtraFields); err != nil {
		return err
	}

	delete(_v.ExtraFields, "namespace")
	delete(_v.ExtraFields, "fix_state")

	if len(_v.ExtraFields) == 0 {
		_v.ExtraFields = nil
	}

	// Cast the new type instance to the original type and assign.
	*v = VulnerabilityExclusionConstraint(_v)
	return nil
}

// PackageExclusionConstraint describes criteria for excluding a match based on package components
type PackageExclusionConstraint struct {
	Name        string                 `json:"name,omitempty"`     // Package name
	Language    string                 `json:"language,omitempty"` // The language ecosystem for a package
	Type        string                 `json:"type,omitempty"`     // Package type
	Version     string                 `json:"version,omitempty"`  // Package version
	Location    string                 `json:"location,omitempty"` // Package location
	ExtraFields map[string]interface{} `json:"-"`
}

func (p PackageExclusionConstraint) Usable() bool {
	return len(p.ExtraFields) == 0
}

func (p *PackageExclusionConstraint) UnmarshalJSON(data []byte) error {
	// Create a new type from the target type to avoid recursion.
	type _packageExclusionConstraint PackageExclusionConstraint

	// Unmarshal into an instance of the new type.
	var _p _packageExclusionConstraint
	if err := json.Unmarshal(data, &_p); err != nil {
		return err
	}

	if err := json.Unmarshal(data, &_p.ExtraFields); err != nil {
		return err
	}

	delete(_p.ExtraFields, "name")
	delete(_p.ExtraFields, "language")
	delete(_p.ExtraFields, "type")
	delete(_p.ExtraFields, "version")
	delete(_p.ExtraFields, "location")

	if len(_p.ExtraFields) == 0 {
		_p.ExtraFields = nil
	}

	// Cast the new type instance to the original type and assign.
	*p = PackageExclusionConstraint(_p)
	return nil
}
