package alert

import (
	"context"
	"fmt"
)

// NowStats answers "is anything on fire right now" for the Dashboards page's
// top strip. Unlike Stats, it takes no window and no team: it reads the
// alert table's current state directly, the same way the sidebar's
// AlertCounts does, but scoped to pages for RemainInJira — see its own doc
// comment.
type NowStats struct {
	// UnresolvedAlerts is every unresolved alert alertstore owns the response
	// for, of any classification: owned, non-internal tenant. Shadow alerts
	// (response is jiralert's — see RemainInJira) and internal-tenant test
	// traffic are excluded, since nobody here is waiting on them.
	UnresolvedAlerts int `json:"unresolved_alerts" validate:"required"`
	// UnresolvedPages is the page subset of UnresolvedAlerts — same scoping.
	UnresolvedPages int `json:"unresolved_pages" validate:"required"`
	// UnackedPages is the subset of UnresolvedPages still open — nobody has
	// picked them up.
	UnackedPages int `json:"unacked_pages" validate:"required"`
	// RemainInJira is open (not yet acked), *not* owned pages: shadow alerts
	// (see docs/design.md, "The Cutover") whose response is jiralert's,
	// already ticketed and paged over there. Recorded here for visibility,
	// not waiting on anyone in alertstore. A shadow alert someone acks here
	// anyway drops out of this count — correct, since it is being handled
	// here now.
	RemainInJira int `json:"remain_in_jira" validate:"required"`
	// RemainInJiraURL is a Jira issue-search link for the deployment's
	// project, filtered to unresolved issues — not resolved by the Store
	// itself, which is DB-only and has no Jira client. The handler fills it
	// in from s.jira, the same way getAlert fills in ExternalIssueURL.
	// Empty when Jira is unconfigured; the client renders the tile as plain
	// text rather than a dead link in that case.
	RemainInJiraURL string `json:"remain_in_jira_url,omitempty"`
}

// ownedExpr reads attrs.owned out of the JSON attrs column. It is a JSON
// boolean, not a string (see AlertAttrs.Owned), so JSON_EXTRACT alone would
// hand back a JSON `true`/`false` value rather than something a `= 'true'`
// comparison could match — JSON_UNQUOTE flattens it to the text "true" or
// "false" the same way it flattens any non-string JSON scalar, matching the
// oncallTeamExpr pattern in stats.go. A rule with no `owned` key at all
// (attrs predating the cutover, or a hand-built fixture) makes JSON_EXTRACT
// return SQL NULL, which every comparison below treats as "not owned" —
// see docs/design.md: "Unlabelled means not ours."
const ownedExpr = "JSON_UNQUOTE(JSON_EXTRACT(attrs, '$.owned'))"

// Now computes NowStats in one query over the whole table's current state —
// no window, no team, nothing paged into Go.
func (s *Store) Now(ctx context.Context) (*NowStats, error) {
	// unresolved_* count only owned (a shadow alert's owned is NULL or 'false',
	// so it drops out), non-internal rows. remain_in_jira is the inverse owned
	// test — the shadow pages — and stays unfiltered by tenant on purpose.
	q := `
		SELECT
			COALESCE(SUM(state != 'resolved' AND ` + ownedExpr + ` = 'true' AND tenant_kind != 'internal'), 0) AS unresolved_alerts,
			COALESCE(SUM(classification = 'page' AND state != 'resolved' AND ` + ownedExpr + ` = 'true' AND tenant_kind != 'internal'), 0) AS unresolved_pages,
			COALESCE(SUM(classification = 'page' AND state = 'open' AND ` + ownedExpr + ` = 'true' AND tenant_kind != 'internal'), 0) AS unacked_pages,
			COALESCE(SUM(classification = 'page' AND state = 'open' AND (` + ownedExpr + ` IS NULL OR ` + ownedExpr + ` != 'true')), 0) AS remain_in_jira
		FROM alert
	`
	var row struct {
		UnresolvedAlerts int `db:"unresolved_alerts"`
		UnresolvedPages  int `db:"unresolved_pages"`
		UnackedPages     int `db:"unacked_pages"`
		RemainInJira     int `db:"remain_in_jira"`
	}
	if err := s.db.GetContext(ctx, &row, q); err != nil {
		return nil, fmt.Errorf("alert now: %w", err)
	}
	return &NowStats{
		UnresolvedAlerts: row.UnresolvedAlerts,
		UnresolvedPages:  row.UnresolvedPages,
		UnackedPages:     row.UnackedPages,
		RemainInJira:     row.RemainInJira,
	}, nil
}
