package vulnerabilitydatabase

import (
	"encoding/json"

	"github.com/deepfence/ThreatMapper/deepfence_utils/log"
	"github.com/deepfence/ThreatMapper/deepfence_utils/sqlite"
)

const (
	VulnerabilityTableName = "vulnerability"
)

// VulnerabilityModel is a struct used to serialize db.Vulnerability information into a sqlite3 DB.
type VulnerabilityModel struct {
	PK                     uint64            `gorm:"primary_key;auto_increment;"`
	ID                     string            `gorm:"column:id"`
	PackageName            string            `gorm:"column:package_name; index:get_vulnerability_index"`
	Namespace              string            `gorm:"column:namespace; index:get_vulnerability_index"`
	PackageQualifiers      sqlite.NullString `gorm:"column:package_qualifiers"`
	VersionConstraint      string            `gorm:"column:version_constraint"`
	VersionFormat          string            `gorm:"column:version_format"`
	CPEs                   sqlite.NullString `gorm:"column:cpes; default:null"`
	RelatedVulnerabilities sqlite.NullString `gorm:"column:related_vulnerabilities; default:null"`
	FixedInVersions        sqlite.NullString `gorm:"column:fixed_in_versions; default:null"`
	FixState               string            `gorm:"column:fix_state"`
	Advisories             sqlite.NullString `gorm:"column:advisories; default:null"`
}

// TableName returns the table which all db.Vulnerability model instances are stored into.
func (m *VulnerabilityModel) TableName() string {
	return VulnerabilityTableName
}

// Inflate generates a db.Vulnerability object from the serialized model instance.
func (m *VulnerabilityModel) Inflate() (Vulnerability, error) {
	var cpes []string
	err := json.Unmarshal(m.CPEs.ToByteSlice(), &cpes)
	if err != nil {
		log.Debug().Msgf("unable to unmarshal CPEs (%+v): %v", m.CPEs, err)
	}

	var related []VulnerabilityReference
	err = json.Unmarshal(m.RelatedVulnerabilities.ToByteSlice(), &related)
	if err != nil {
		log.Debug().Msgf("unable to unmarshal related vulnerabilities (%+v): %v", m.RelatedVulnerabilities, err)
	}

	var advisories []Advisory

	err = json.Unmarshal(m.Advisories.ToByteSlice(), &advisories)
	if err != nil {
		log.Debug().Msgf("unable to unmarshal advisories (%+v): %v", m.Advisories, err)
	}

	var versions []string
	err = json.Unmarshal(m.FixedInVersions.ToByteSlice(), &versions)
	if err != nil {
		log.Debug().Msgf("unable to unmarshal versions (%+v): %v", m.FixedInVersions, err)
	}

	return Vulnerability{
		ID:                     m.ID,
		PackageName:            m.PackageName,
		Namespace:              m.Namespace,
		VersionConstraint:      m.VersionConstraint,
		VersionFormat:          m.VersionFormat,
		CPEs:                   cpes,
		RelatedVulnerabilities: related,
		Fix: Fix{
			Versions: versions,
			State:    FixState(m.FixState),
		},
		Advisories: advisories,
	}, nil
}

// Vulnerability represents the minimum data fields necessary to perform package-to-vulnerability matching. This can represent a CVE, 3rd party advisory, or any source that relates back to a CVE.
type Vulnerability struct {
	ID                     string                   `json:"id"`                      // The identifier of the vulnerability or advisory
	PackageName            string                   `json:"package_name"`            // The name of the package that is vulnerable
	Namespace              string                   `json:"namespace"`               // The ecosystem where the package resides
	VersionConstraint      string                   `json:"version_constraint"`      // The version range which the given package is vulnerable
	VersionFormat          string                   `json:"version_format"`          // The format which all version fields should be interpreted as
	CPEs                   []string                 `json:"cpes"`                    // The CPEs which are considered vulnerable
	RelatedVulnerabilities []VulnerabilityReference `json:"related_vulnerabilities"` // Other Vulnerabilities that are related to this one (e.g. GHSA relate to CVEs, or how distro CVE relates to NVD record)
	Fix                    Fix                      `json:"fix"`                     // All information about fixed versions
	Advisories             []Advisory               `json:"advisories"`              // Any vendor advisories about fixes or other notifications about this vulnerability
}

type VulnerabilityReference struct {
	ID        string `json:"id"`
	Namespace string `json:"namespace"`
}

type FixState string

const (
	UnknownFixState FixState = "unknown"
	FixedState      FixState = "fixed"
	NotFixedState   FixState = "not-fixed"
	WontFixState    FixState = "wont-fix"
)

// Fix represents all information about known fixes for a stated vulnerability.
type Fix struct {
	Versions []string `json:"versions"` // The version(s) which this particular vulnerability was fixed in
	State    FixState `json:"state"`
}

// Advisory represents published statements regarding a vulnerability (and potentially about it's resolution).
type Advisory struct {
	ID   string `json:"id"`
	Link string `json:"link"`
}
