package client

import "encoding/json"

// ServiceStatus is a single entry from the T1007 System Service Discovery
// allowlist — read-only, fixed list, no enumeration of arbitrary services.
type ServiceStatus struct {
	Name        string `json:"name"`
	DisplayName string `json:"display_name,omitempty"`
	Status      string `json:"status"`     // running | stopped | not_found
	StartType   string `json:"start_type"` // auto | manual | disabled | unknown
}

// PostureReport is a read-only ATT&CK T1685/T1686 defense-impairment baseline
// combined with a Tenable-style patch-exposure snapshot and T1007 service audit.
// Every field is an observation — the agent never modifies any security control.
type PostureReport struct {
	// ── Antivirus / Defender ──────────────────────────────────────────────────
	DefenderEnabled *bool    `json:"defender_enabled,omitempty"`
	DefenderRTP     *bool    `json:"defender_rtp,omitempty"`
	AVProducts      []string `json:"av_products,omitempty"`

	// ── Firewall — per-profile (T1686.003) ───────────────────────────────────
	FirewallDomain  *bool `json:"firewall_domain,omitempty"`
	FirewallPrivate *bool `json:"firewall_private,omitempty"`
	FirewallPublic  *bool `json:"firewall_public,omitempty"`

	// ── SSH ───────────────────────────────────────────────────────────────────
	SSHListening *bool `json:"ssh_listening,omitempty"`

	// ── Patch exposure (Tenable-style) ───────────────────────────────────────
	PendingUpdates *int    `json:"pending_updates,omitempty"` // -1 = unknown/timed out
	LastPatch      *string `json:"last_patch,omitempty"`      // ISO date YYYY-MM-DD
	LastPatchDays  *int    `json:"last_patch_days,omitempty"`
	PatchRecent    *bool   `json:"patch_recent,omitempty"` // last_patch_days <= 30
	RebootPending  *bool   `json:"reboot_pending,omitempty"`

	// ── T1007 System Service Discovery — fixed allowlist only ────────────────
	Services []ServiceStatus `json:"services,omitempty"`

	// ── Process context ───────────────────────────────────────────────────────
	AgentElevated  *bool `json:"agent_elevated,omitempty"`
	AgentServiceOK *bool `json:"agent_service_ok,omitempty"`

	// ── Computed score (0-100, 5 pillars × 20) ───────────────────────────────
	PostureScore int `json:"posture_score"`
}

// computePostureScore produces a 0-100 score across 5 equal pillars.
func computePostureScore(r *PostureReport) int {
	if r == nil {
		return 0
	}
	score := 0

	// Pillar 1 — AV active
	avOK := (r.DefenderEnabled != nil && *r.DefenderEnabled) || len(r.AVProducts) > 0
	if avOK {
		score += 20
	}

	// Pillar 2 — At least one firewall profile enabled
	for _, p := range []*bool{r.FirewallDomain, r.FirewallPrivate, r.FirewallPublic} {
		if p != nil && *p {
			score += 20
			break
		}
	}

	// Pillar 3 — SSH reachable (enables Crucible access)
	if boolTrue(r.SSHListening) {
		score += 20
	}

	// Pillar 4 — Patch health:
	//   20 pts : recently patched AND no reboot pending
	//   10 pts : one of the two conditions is true
	//    0 pts : stale AND/OR reboot required
	recentOK := boolTrue(r.PatchRecent)
	rebootOK := r.RebootPending == nil || !*r.RebootPending
	if recentOK && rebootOK {
		score += 20
	} else if recentOK || rebootOK {
		score += 10
	}

	// Pillar 5 — Agent alive (always true while connected)
	if boolTrue(r.AgentServiceOK) {
		score += 20
	}

	return score
}

func boolTrue(v *bool) bool  { return v != nil && *v }
func boolPtr(v bool) *bool   { return &v }
func intPtr(v int) *int       { return &v }
func strPtr(v string) *string { return &v }

func boolPoints(v *bool) int {
	if v == nil {
		return -1
	}
	if *v {
		return 20
	}
	return 0
}

// JSON serialises the report, recomputing the score first.
func (r *PostureReport) JSON() string {
	r.PostureScore = computePostureScore(r)
	b, _ := json.Marshal(r)
	return string(b)
}
