package model

import (
	"encoding/json"
	"fmt"

	sqlite "github.com/anchore/grype/grype/db/internal/sqlite"
	v5 "github.com/anchore/grype/grype/db/v5"
	"github.com/anchore/grype/grype/db/v5/pkg/qualifier"
)

const (
	VulnerabilityTableName    = "vulnerability"
	GetVulnerabilityIndexName = "get_vulnerability_index"
)

// 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"`
}

// NewVulnerabilityModel generates a new model from a db.Vulnerability struct.
func NewVulnerabilityModel(vulnerability v5.Vulnerability) VulnerabilityModel {
	return VulnerabilityModel{
		ID:                     vulnerability.ID,
		PackageName:            vulnerability.PackageName,
		Namespace:              vulnerability.Namespace,
		PackageQualifiers:      sqlite.ToNullString(vulnerability.PackageQualifiers),
		VersionConstraint:      vulnerability.VersionConstraint,
		VersionFormat:          vulnerability.VersionFormat,
		FixedInVersions:        sqlite.ToNullString(vulnerability.Fix.Versions),
		FixState:               string(vulnerability.Fix.State),
		Advisories:             sqlite.ToNullString(vulnerability.Advisories),
		CPEs:                   sqlite.ToNullString(vulnerability.CPEs),
		RelatedVulnerabilities: sqlite.ToNullString(vulnerability.RelatedVulnerabilities),
	}
}

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

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

	var related []v5.VulnerabilityReference
	err = json.Unmarshal(m.RelatedVulnerabilities.ToByteSlice(), &related)
	if err != nil {
		return v5.Vulnerability{}, fmt.Errorf("unable to unmarshal related vulnerabilities (%+v): %w", m.RelatedVulnerabilities, err)
	}

	var advisories []v5.Advisory

	err = json.Unmarshal(m.Advisories.ToByteSlice(), &advisories)
	if err != nil {
		return v5.Vulnerability{}, fmt.Errorf("unable to unmarshal advisories (%+v): %w", m.Advisories, err)
	}

	var versions []string
	err = json.Unmarshal(m.FixedInVersions.ToByteSlice(), &versions)
	if err != nil {
		return v5.Vulnerability{}, fmt.Errorf("unable to unmarshal versions (%+v): %w", m.FixedInVersions, err)
	}

	pkgQualifiers, err := qualifier.FromJSON(m.PackageQualifiers.ToByteSlice())
	if err != nil {
		return v5.Vulnerability{}, fmt.Errorf("unable to unmarshal package_qualifiers (%+v): %w", m.PackageQualifiers, err)
	}

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