package model

import (
	"encoding/json"
	"fmt"

	v3 "github.com/anchore/grype/grype/db/v3"
)

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"`
	VersionConstraint      string `gorm:"column:version_constraint"`
	VersionFormat          string `gorm:"column:version_format"`
	CPEs                   string `gorm:"column:cpes"`
	RelatedVulnerabilities string `gorm:"column:related_vulnerabilities"`
	FixedInVersions        string `gorm:"column:fixed_in_versions"`
	FixState               string `gorm:"column:fix_state"`
	Advisories             string `gorm:"column:advisories"`
}

// NewVulnerabilityModel generates a new model from a db.Vulnerability struct.
func NewVulnerabilityModel(vulnerability v3.Vulnerability) VulnerabilityModel {
	cpes, err := json.Marshal(vulnerability.CPEs)
	if err != nil {
		// TODO: just no
		panic(err)
	}

	related, err := json.Marshal(vulnerability.RelatedVulnerabilities)
	if err != nil {
		// TODO: just no
		panic(err)
	}

	advisories, err := json.Marshal(vulnerability.Advisories)
	if err != nil {
		// TODO: just no
		panic(err)
	}

	fixedInVersions, err := json.Marshal(vulnerability.Fix.Versions)
	if err != nil {
		// TODO: just no
		panic(err)
	}

	return VulnerabilityModel{
		ID:                     vulnerability.ID,
		PackageName:            vulnerability.PackageName,
		Namespace:              vulnerability.Namespace,
		VersionConstraint:      vulnerability.VersionConstraint,
		VersionFormat:          vulnerability.VersionFormat,
		FixedInVersions:        string(fixedInVersions),
		FixState:               string(vulnerability.Fix.State),
		Advisories:             string(advisories),
		CPEs:                   string(cpes),
		RelatedVulnerabilities: string(related),
	}
}

// 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() (v3.Vulnerability, error) {
	var cpes []string
	err := json.Unmarshal([]byte(m.CPEs), &cpes)
	if err != nil {
		return v3.Vulnerability{}, fmt.Errorf("unable to unmarshal CPEs (%+v): %w", m.CPEs, err)
	}

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

	var advisories []v3.Advisory
	err = json.Unmarshal([]byte(m.Advisories), &advisories)
	if err != nil {
		return v3.Vulnerability{}, fmt.Errorf("unable to unmarshal advisories (%+v): %w", m.Advisories, err)
	}

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

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