package store

import "fmt"

// Customer DELETE residue (v0.70.0, R-25b follow-up).
//
// The v0.69.0 cascade tore down hosts, external state and the customer record — and a fully deleted
// customer STILL came back on the Customers list, because `GetCustomers()` derives the list purely
// from the REPORT stream (`SELECT ... FROM reports ... GROUP BY customer_id`). Nothing in any
// lifecycle tier ever deleted a report. Worse than cosmetic: the staleness and offsite checkers
// iterate the same report-derived list, so a deleted customer kept raising `offsite_stale` and kept
// emailing the operator — observed live on `demo-vm-felhom`, deleted 2026-07-18, still alerting
// 2026-07-21.
//
// Two of these tables are not telemetry at all but CREDENTIAL-BEARING, and outliving their customer
// is a security defect rather than noise:
//
//   - `appliance_registrations` — a `token_hash` + `status='delivered'` row binding a box to the
//     customer id. Deleting the row returns a still-living box to the unclaimed pool on its next
//     registration, which is exactly the right state for a decommissioned appliance.
//   - `selfbind_tokens` — an unconsumed 7-day bind token would remain a working path to bind a box
//     to a customer that no longer exists.
//
// What deliberately SURVIVES (unchanged from every other tier): `events` (the audit stream),
// `notification_log` (the send-attempt audit), `host_deletions` and `customer_resets` (F-14
// provenance). The audit trail outlives every lifecycle tier — that rule is not relaxed here.

// CustomerResidue counts what a customer has left behind OUTSIDE the config row: the report-derived
// state that keeps a deleted customer visible and alerting, plus the credential-bearing bindings.
// Counts only — never a token, hash or payload.
type CustomerResidue struct {
	Reports                int
	AppTelemetry           int
	AppLogTails            int
	LogTailRequests        int
	NotificationPrefs      int
	SelfBindTokens         int
	ApplianceRegistrations int
}

// Total is the single number the "is there anything left at all?" decision hangs on.
func (r *CustomerResidue) Total() int {
	if r == nil {
		return 0
	}
	return r.Reports + r.AppTelemetry + r.AppLogTails + r.LogTailRequests +
		r.NotificationPrefs + r.SelfBindTokens + r.ApplianceRegistrations
}

// residueQueries is the ONE list both the counter and the purge walk, so a table can never be
// counted-but-not-purged (or purged-but-not-counted) — the two drift silently otherwise.
var residueQueries = []struct {
	table string
	dst   func(*CustomerResidue) *int
}{
	{"reports", func(r *CustomerResidue) *int { return &r.Reports }},
	{"app_telemetry", func(r *CustomerResidue) *int { return &r.AppTelemetry }},
	{"app_log_tails", func(r *CustomerResidue) *int { return &r.AppLogTails }},
	{"log_tail_requests", func(r *CustomerResidue) *int { return &r.LogTailRequests }},
	{"customer_notifications", func(r *CustomerResidue) *int { return &r.NotificationPrefs }},
	{"selfbind_tokens", func(r *CustomerResidue) *int { return &r.SelfBindTokens }},
	{"appliance_registrations", func(r *CustomerResidue) *int { return &r.ApplianceRegistrations }},
}

// CustomerResidue counts the customer's report-derived + binding residue. Read-only.
func (s *Store) CustomerResidue(customerID string) (*CustomerResidue, error) {
	res := &CustomerResidue{}
	for _, q := range residueQueries {
		if err := s.db.QueryRow(`SELECT COUNT(*) FROM `+q.table+` WHERE customer_id = ?`, customerID).
			Scan(q.dst(res)); err != nil {
			return nil, fmt.Errorf("CustomerResidue %s: count %s: %w", customerID, q.table, err)
		}
	}
	return res, nil
}

// PurgeCustomerResidue removes the report-derived state and the credential-bearing bindings in ONE
// transaction — the leg that actually makes a deleted customer disappear from the Customers list and
// stop alerting. Idempotent: a re-run deletes nothing extra. It NEVER touches events,
// notification_log, host_deletions or customer_resets.
func (s *Store) PurgeCustomerResidue(customerID string) error {
	if customerID == "" {
		return fmt.Errorf("PurgeCustomerResidue: empty customer_id")
	}
	tx, err := s.db.Begin()
	if err != nil {
		return err
	}
	defer tx.Rollback()
	for _, q := range residueQueries {
		if _, err := tx.Exec(`DELETE FROM `+q.table+` WHERE customer_id = ?`, customerID); err != nil {
			return fmt.Errorf("PurgeCustomerResidue %s: purge %s: %w", customerID, q.table, err)
		}
	}
	return tx.Commit()
}
