// Package agent is alertstore's side of the agent contract: which agents are
// dispatched an alert, and what they reported back.
//
// The contract is deliberately thin in both directions. Outbound, alertstore
// posts an alert to a URL and knows nothing else about what is there. Inbound,
// an agent writes a run result and nothing else — it cannot ack, resolve,
// assign, or mute. Everything an agent produces lands next to the human record
// rather than inside it, which is what keeps the two independently readable and
// makes the agent's accuracy measurable against a verdict it did not write.
package agent

import (
	"encoding/json"
	"time"

	"github.com/tidbcloud/alertstore/pkg/jsonutil"
)

// Trigger kinds — why a run exists.
const (
	TriggerAlertCreated = "alert_created" // alertstore dispatched it on firing
	TriggerManual       = "manual"        // a human asked for it
	TriggerHandoff      = "handoff"       // another run asked for this agent
)

// Run statuses. The agent owns every transition after dispatch; a run that
// never reports stays `dispatched`, which is a finding rather than a gap.
const (
	StatusDispatched = "dispatched"
	StatusRunning    = "running"
	StatusSucceeded  = "succeeded"
	StatusFailed     = "failed"
)

// Verdicts. The three diagnoses pair with a resolution (noise ↔ noise,
// self_recovered ↔ self_recovered, escalation ↔ handled) so agreement is one
// join. Mitigated is a claim of action, scored by override instead; reserved
// until an agent may act. Unknown is kept apart from escalation so the
// scorecard never counts an unsure agent as a confident one.
const (
	VerdictNoise         = "noise"
	VerdictSelfRecovered = "self_recovered"
	VerdictMitigated     = "mitigated"
	VerdictEscalation    = "escalation"
	VerdictUnknown       = "unknown"
)

// Disclosure decides whether a run's verdict may be rendered before the human
// records their own resolution. See the migration for why this is not a feature
// flag that can be added later.
const (
	DisclosureShown = "shown"
	DisclosureHeld  = "held"
)

// ValidVerdict reports whether v is one this store will record. An unknown
// string is rejected at the API rather than stored, because a verdict that
// matches nothing silently drops out of every agreement query instead of
// showing up as a bad row.
func ValidVerdict(v string) bool {
	switch v {
	case VerdictNoise, VerdictSelfRecovered, VerdictMitigated, VerdictEscalation, VerdictUnknown:
		return true
	}
	return false
}

// ValidStatus reports whether s is a status an agent may report. Dispatched is
// excluded: alertstore writes it, and an agent moving a run back to it would
// erase the fact that it had started.
func ValidStatus(s string) bool {
	switch s {
	case StatusRunning, StatusSucceeded, StatusFailed:
		return true
	}
	return false
}

// Subscription routes alerts to one agent. Filter is a
// subscription.SubscriptionFilter — the same matcher subscriptions and mute
// rules use.
type Subscription struct {
	ID        string          `db:"id" json:"id"`
	Name      string          `db:"name" json:"name"`
	Endpoint  string          `db:"endpoint" json:"endpoint"`
	Filter    json.RawMessage `db:"filter" json:"filter" swaggertype:"object"`
	Enabled   bool            `db:"enabled" json:"enabled"`
	CreatedBy string          `db:"created_by" json:"created_by"`
	CreatedAt time.Time       `db:"created_at" json:"created_at"`
	UpdatedAt time.Time       `db:"updated_at" json:"updated_at"`
}

// Run is one agent's work on one alert.
type Run struct {
	ID          int64  `db:"id" json:"id"`
	AlertID     string `db:"alert_id" json:"alert_id"`
	Agent       string `db:"agent" json:"agent"`
	TriggerKind string `db:"trigger_kind" json:"trigger_kind" enums:"alert_created,manual,handoff"`
	DedupKey    string `db:"dedup_key" json:"-"`
	ParentRunID *int64 `db:"parent_run_id" json:"parent_run_id,omitempty"`

	Status     string                      `db:"status" json:"status" enums:"dispatched,running,succeeded,failed"`
	Verdict    string                      `db:"verdict" json:"verdict" enums:"noise,self_recovered,mitigated,escalation,unknown"`
	Summary    string                      `db:"summary" json:"summary"`
	SessionRef string                      `db:"session_ref" json:"session_ref"`
	Detail     jsonutil.NullableRawMessage `db:"detail" json:"detail,omitempty" swaggertype:"object"`
	Disclosure string                      `db:"disclosure" json:"disclosure" enums:"shown,held"`

	StartedAt  time.Time  `db:"started_at" json:"started_at"`
	FinishedAt *time.Time `db:"finished_at" json:"finished_at,omitempty"`
}

// Terminal reports whether the agent has finished with this run, either way.
func (r *Run) Terminal() bool {
	return r.Status == StatusSucceeded || r.Status == StatusFailed
}
