// Package audit records system-affecting actions (signal writes by users or the
// control-logic engine, interface and control-logic mutations) to an append-only
// SQLite log that audit staff can query later. It is enabled via [audit] in the
// config; when disabled a no-op Recorder is used so call sites need no guards.
package audit

import "time"

// Actor types distinguish human-initiated actions from automated ones.
const (
	ActorUser   = "user"   // action attributed to an authenticated end-user
	ActorSystem = "system" // action performed by the control-logic engine
)

// Outcomes record whether the action succeeded.
const (
	OutcomeOK    = "ok"
	OutcomeError = "error"
)

// Event is a single audit record. Optional fields are omitted from the database
// when empty. Time defaults to the current time when zero.
type Event struct {
	Time      time.Time `json:"time"`
	Actor     string    `json:"actor"`            // username, or graph name for system actions
	ActorType string    `json:"actorType"`        // ActorUser | ActorSystem
	Action    string    `json:"action"`           // e.g. "signal.write", "interface.update"
	DS        string    `json:"ds,omitempty"`     // data source (signal writes)
	Signal    string    `json:"signal,omitempty"` // signal / target name
	Value     string    `json:"value,omitempty"`  // serialised written value
	Detail    string    `json:"detail,omitempty"` // free-form context (id, graph, trigger)
	IP        string    `json:"ip,omitempty"`     // client address (user actions)
	Outcome   string    `json:"outcome"`          // OutcomeOK | OutcomeError
	Error     string    `json:"error,omitempty"`  // message when Outcome==OutcomeError
}

// Filter selects a subset of events for Query. Zero-valued fields are ignored.
type Filter struct {
	Start  time.Time
	End    time.Time
	Actor  string
	Action string
	DS     string
	Signal string
	Limit  int // <=0 means a default cap is applied
}

// Recorder appends events and answers queries. Implementations must be safe for
// concurrent use and Record must never block the caller for long.
type Recorder interface {
	Record(Event)
	Query(Filter) ([]Event, error)
	Close() error
}

// nopRecorder is used when auditing is disabled. It discards every event.
type nopRecorder struct{}

func (nopRecorder) Record(Event)                  {}
func (nopRecorder) Query(Filter) ([]Event, error) { return []Event{}, nil }
func (nopRecorder) Close() error                  { return nil }

// Nop returns a Recorder that discards everything. Call sites can hold a Recorder
// unconditionally and call Record without checking whether auditing is enabled.
func Nop() Recorder { return nopRecorder{} }
