package models

import (
	"time"

	"go.mongodb.org/mongo-driver/v2/bson"
)

// Finding states.
const (
	FindingOpen     = "open"
	FindingFixed    = "fixed"
	FindingAccepted = "accepted"
)

// Severities. Lowercase and fixed; SeverityRank orders them.
const (
	SeverityUnknown  = "unknown"
	SeverityLow      = "low"
	SeverityMedium   = "medium"
	SeverityHigh     = "high"
	SeverityCritical = "critical"
)

// SeverityRank orders severities for threshold comparisons. An unrecognised
// value ranks lowest rather than panicking: severity comes from a third-party
// feed, and an unexpected string must not stop a scan.
func SeverityRank(s string) int {
	switch s {
	case SeverityCritical:
		return 4
	case SeverityHigh:
		return 3
	case SeverityMedium:
		return 2
	case SeverityLow:
		return 1
	default:
		return 0
	}
}

// Acceptance records a decision someone will be asked to justify, so who, why
// and until when all live on the document as well as in the audit log.
type Acceptance struct {
	By     string    `bson:"by" json:"by"`
	Reason string    `bson:"reason" json:"reason"`
	Until  time.Time `bson:"until" json:"until"`
	At     time.Time `bson:"at" json:"at"`
}

// VulnFinding is one vulnerable package on one server.
//
// Findings are never deleted when a package is patched: the state moves to
// "fixed" with FixedAt stamped, which is what keeps "what did we remediate last
// quarter" answerable.
type VulnFinding struct {
	ID         bson.ObjectID `bson:"_id,omitempty" json:"id"`
	InstanceID string        `bson:"instance_id" json:"-"`
	ServerID   string        `bson:"server_id" json:"server_id"`

	CVEID       string `bson:"cve_id" json:"cve_id"`
	PackageName string `bson:"package_name" json:"package_name"`
	Installed   string `bson:"installed_version" json:"installed_version"`
	// FixedIn empty means no vendor fix has been published. That is a real and
	// common state and must never be conflated with "not vulnerable" — it is
	// the finding most in need of acceptance, since there is nothing to patch.
	FixedIn    string   `bson:"fixed_in,omitempty" json:"fixed_in,omitempty"`
	Severity   string   `bson:"severity" json:"severity"`
	CVSSScore  float64  `bson:"cvss_score,omitempty" json:"cvss_score,omitempty"`
	Title      string   `bson:"title,omitempty" json:"title,omitempty"`
	References []string `bson:"references,omitempty" json:"references,omitempty"`

	State     string      `bson:"state" json:"state"`
	FirstSeen time.Time   `bson:"first_seen" json:"first_seen"`
	LastSeen  time.Time   `bson:"last_seen" json:"last_seen"`
	FixedAt   *time.Time  `bson:"fixed_at,omitempty" json:"fixed_at,omitempty"`
	Accepted  *Acceptance `bson:"accepted,omitempty" json:"accepted,omitempty"`
}

// VulnDBMeta is a singleton and deliberately carries no instance_id: the
// vulnerability database is a property of the deployment, not of a tenant.
// Same reasoning as the migrations collection, and the reason vulndb_meta is
// absent from ScopedCollections.
type VulnDBMeta struct {
	ID             bson.ObjectID `bson:"_id,omitempty" json:"-"`
	DBVersion      int           `bson:"db_version" json:"db_version"`
	PulledAt       time.Time     `bson:"pulled_at" json:"pulled_at"`
	LastFullScanAt time.Time     `bson:"last_full_scan_at,omitempty" json:"last_full_scan_at,omitempty"`
	LastError      string        `bson:"last_error,omitempty" json:"last_error,omitempty"`
}

// VulnAlertRule routes newly opened findings to notification channels.
//
// Tags resolve through services.ResolveTargets rather than a second matcher:
// that function is already the single answer to which servers a selector
// touches, and a rule disagreeing with a workflow about what env:prod means
// would be worse than having no filter.
type VulnAlertRule struct {
	ID          bson.ObjectID     `bson:"_id,omitempty" json:"id"`
	InstanceID  string            `bson:"instance_id" json:"-"`
	Name        string            `bson:"name" json:"name"`
	Enabled     bool              `bson:"enabled" json:"enabled"`
	MinSeverity string            `bson:"min_severity" json:"min_severity"`
	Tags        map[string]string `bson:"tags,omitempty" json:"tags,omitempty"`
	ChannelIDs  []string          `bson:"channel_ids" json:"channel_ids"`
	CreatedAt   time.Time         `bson:"created_at" json:"created_at"`
	UpdatedAt   time.Time         `bson:"updated_at" json:"updated_at"`
}
