// Package audit provides a thin convenience wrapper around
// engine.Audit tailored to mTLS / cert lifecycle events. It exists so
// that cert, transport, and daemon code can call a small, semantically
// clear API (Emit with explicit action + result) without depending on
// the more general-purpose engine.Audit.
package audit

import (
	"context"
	"fmt"
	"log/slog"

	"git.cloudinit.dev/coreci/orca/internal/engine"
)

// Result enumerates the result strings persisted to audit_log. Keeping
// these as constants (rather than free-form strings) prevents typos at
// call sites and makes log analytics trivial.
type Result string

const (
	ResultSuccess Result = "success"
	ResultFailure Result = "failure"
	ResultDenied  Result = "denied"
)

// Action enumerates the cert / handshake event names used across the
// security surface. Matches REQ-038 / P01 must-haves:
//
//	cert.issued           — a CSR was signed, server cert persisted
//	cert.renewed          — a server cert was re-issued (rotation)
//	cert.joined           — a node joined the trust domain (CA pinned)
//	node.handshake_ok     — mTLS handshake succeeded
//	node.handshake_failed — mTLS handshake failed
type Action string

const (
	ActionCertIssued        Action = "cert.issued"
	ActionCertRenewed       Action = "cert.renewed"
	ActionCertJoined        Action = "cert.joined"
	ActionNodeHandshakeOK   Action = "node.handshake_ok"
	ActionNodeHandshakeFail Action = "node.handshake_failed"
)

// Audit wraps engine.Audit with a cert/handshake-focused API.
type Audit struct {
	engine *engine.Audit
}

// New constructs an Audit backed by the given engine.Audit. The engine
// instance persists to the audit_log table; the wrapper just shapes
// the call signature.
func New(e *engine.Audit) *Audit {
	return &Audit{engine: e}
}

// Emit persists an audit entry. The `event` is a free-form description
// that ends up in the resource field, paired with action + result. Use
// the Action* constants for `action`; free-form strings for `event` are
// allowed for extensibility but should be stable for analytics.
func (a *Audit) Emit(ctx context.Context, action Action, event string, result Result, metadata map[string]any) {
	if a == nil || a.engine == nil {
		return
	}
	// Resource field is conventionally <event>:<id>; we just use event
	// as-is here. Callers can stuff the relevant id into metadata.
	a.engine.Record(ctx, "security", string(action), event, string(result), nil, metadata)
}

// EmitWithErr persists a failure entry whose err is also recorded in the
// audit_log.error column. Use this for handshake failures and similar
// error paths where the underlying error is useful for postmortem.
func (a *Audit) EmitWithErr(ctx context.Context, action Action, event string, err error, metadata map[string]any) {
	if a == nil || a.engine == nil {
		return
	}
	a.engine.Record(ctx, "security", string(action), event, string(ResultFailure), err, metadata)
}

// LogHandshakeOK emits a structured slog record for a successful mTLS
// handshake. This is a SEPARATE log line from the audit_log entry —
// structured slog is for operators; audit_log is for compliance.
func LogHandshakeOK(log *slog.Logger, peer, certFP string) {
	if log == nil {
		return
	}
	log.Info("mtls.handshake",
		slog.String("event", "mtls.handshake"),
		slog.String("result", "ok"),
		slog.String("peer", peer),
		slog.String("cert_fp", certFP),
	)
}

// LogHandshakeFailed emits a structured slog record for a failed mTLS
// handshake. Per REQ-038, the fields are: event=mtls.handshake, peer,
// cert_fp (may be empty if no cert was presented), err.
func LogHandshakeFailed(log *slog.Logger, peer, certFP string, err error) {
	if log == nil {
		return
	}
	attrs := []any{
		slog.String("event", "mtls.handshake"),
		slog.String("result", "failed"),
		slog.String("peer", peer),
		slog.String("cert_fp", certFP),
	}
	if err != nil {
		attrs = append(attrs, slog.String("err", err.Error()))
	}
	log.Warn("mtls.handshake", attrs...)
}

// String converts an Action to its canonical string form. Useful in
// tests and CLI surface.
func (a Action) String() string { return string(a) }

// String converts a Result to its canonical string form.
func (r Result) String() string { return string(r) }

// FormatAction formats an action+result pair as "action=... result=...",
// used by callers building structured log lines.
func FormatAction(action Action, result Result) string {
	return fmt.Sprintf("action=%s result=%s", action, result)
}
