package model

import (
	"encoding/json"
	"fmt"

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

const (
	VulnerabilityMatchExclusionTableName    = "vulnerability_match_exclusion"
	GetVulnerabilityMatchExclusionIndexName = "get_vulnerability_match_exclusion_index"
)

// VulnerabilityMatchExclusionModel is a struct used to serialize db.VulnerabilityMatchExclusion information into a sqlite3 DB.
type VulnerabilityMatchExclusionModel struct {
	PK            uint64            `gorm:"primary_key;auto_increment;"`
	ID            string            `gorm:"column:id; index:get_vulnerability_match_exclusion_index"`
	Constraints   sqlite.NullString `gorm:"column:constraints; default:null"`
	Justification string            `gorm:"column:justification"`
}

// NewVulnerabilityMatchExclusionModel generates a new model from a db.VulnerabilityMatchExclusion struct.
func NewVulnerabilityMatchExclusionModel(v v5.VulnerabilityMatchExclusion) VulnerabilityMatchExclusionModel {
	return VulnerabilityMatchExclusionModel{
		ID:            v.ID,
		Constraints:   sqlite.ToNullString(v.Constraints),
		Justification: v.Justification,
	}
}

// TableName returns the table which all db.VulnerabilityMatchExclusion model instances are stored into.
func (VulnerabilityMatchExclusionModel) TableName() string {
	return VulnerabilityMatchExclusionTableName
}

// Inflate generates a db.VulnerabilityMatchExclusion object from the serialized model instance.
func (m *VulnerabilityMatchExclusionModel) Inflate() (*v5.VulnerabilityMatchExclusion, error) {
	// It's important that we only utilise exclusion constraints that are compatible with this version of Grype,
	// so if any unknown fields are encountered then ignore that constraint.

	var constraints []v5.VulnerabilityMatchExclusionConstraint
	err := json.Unmarshal(m.Constraints.ToByteSlice(), &constraints)
	if err != nil {
		return nil, fmt.Errorf("unable to unmarshal vulnerability match exclusion constraints (%+v): %w", m.Constraints, err)
	}

	var compatibleConstraints []v5.VulnerabilityMatchExclusionConstraint

	if len(constraints) > 0 {
		for _, c := range constraints {
			if !c.Usable() {
				log.Debugf("skipping incompatible vulnerability match constraint for vuln id=%s, constraint=%+v", m.ID, c)
			} else {
				compatibleConstraints = append(compatibleConstraints, c)
			}
		}

		// If there were constraints and none were compatible, the entire record is not usable by this version of Grype
		if len(compatibleConstraints) == 0 {
			return nil, nil
		}
	}

	return &v5.VulnerabilityMatchExclusion{
		ID:            m.ID,
		Constraints:   compatibleConstraints,
		Justification: m.Justification,
	}, nil
}
