// Package automation — audit_retention.go
//
// Retention runner for _System_FlowAuditLog. Supports two trim modes:
//   - MaxAge: delete rows whose CreatedDate is older than the configured age.
//   - MaxRows: keep only the N newest rows; delete the oldest surplus.
//
// The runner is cancellable (context-based) and is safe to run periodically
// via a ticker or scheduler. Production startup calls RunPeriodic.
package automation

import (
	"context"
	"fmt"
	"log"
	"time"

	"github.com/nexuscrm/kernel/pkg/schema"
	"github.com/nexuscrm/kernel/pkg/schemacatalog"
)

// systemTableDefinitions returns the parsed system-table catalog, preserving the
// historical fail-fast behavior of bootstrap.GetSystemTableDefinitions: a
// malformed embedded catalog is a build/embed defect, so it aborts the process
// rather than propagating. The catalog now lives in the kernel schemacatalog
// package (error-returning); this tiny wrapper keeps the fatal-on-error contract.
func systemTableDefinitions() []schema.TableDefinition {
	defs, err := schemacatalog.SystemTableDefinitions()
	if err != nil {
		log.Fatalf("Failed to parse system_tables.json: %v", err)
	}
	return defs
}

// RetentionConfig configures the retention strategy.
// Exactly one of MaxAge or MaxRows should be non-zero; if both are set both
// are applied (MaxAge first, then MaxRows on the survivors).
type RetentionConfig struct {
	// MaxAge is the maximum age of rows to keep. Rows older than
	// time.Now().Add(-MaxAge) are deleted. Zero means no age-based trim.
	MaxAge time.Duration

	// MaxRows is the maximum total number of rows to retain. When the table
	// has more than MaxRows rows, the oldest (by CreatedDate) are deleted until
	// only MaxRows remain. Zero means no row-count-cap.
	MaxRows int64
}

// Retention periodically trims _System_FlowAuditLog according to RetentionConfig.
type Retention struct {
	store  AuditStore
	config RetentionConfig
}

// NewRetention creates a Retention runner backed by store.
func NewRetention(store AuditStore, config RetentionConfig) *Retention {
	return &Retention{store: store, config: config}
}

// NewRetentionFromTableDefinition constructs a Retention by reading
// RetentionMaxRows and RetentionMaxAgeSeconds from the _System_FlowAuditLog
// TableDefinition registered in the kernel system-table catalog.
// Returns an error when the table is not found in the definitions.
func NewRetentionFromTableDefinition(tableName string, store AuditStore) (*Retention, error) {
	defs := systemTableDefinitions()
	for _, d := range defs {
		if d.TableName == tableName {
			cfg := RetentionConfig{
				MaxRows: int64(d.RetentionMaxRows),
			}
			if d.RetentionMaxAgeSeconds > 0 {
				cfg.MaxAge = time.Duration(d.RetentionMaxAgeSeconds) * time.Second
			}
			return &Retention{store: store, config: cfg}, nil
		}
	}
	return nil, fmt.Errorf("audit retention: table %q not found in system table definitions", tableName)
}

// RunPeriodic starts a background goroutine that calls Run every interval.
// It returns when ctx is cancelled. Intended for production startup.
func (r *Retention) RunPeriodic(ctx context.Context, interval time.Duration) {
	go func() {
		ticker := time.NewTicker(interval)
		defer ticker.Stop()
		for {
			select {
			case <-ctx.Done():
				return
			case <-ticker.C:
				if _, err := r.Run(ctx); err != nil {
					log.Printf("audit retention: periodic run error: %v", err)
				}
			}
		}
	}()
}

// Run performs a single retention pass. It is intended to be called from a
// scheduler or periodic ticker. The context governs cancellation / deadlines.
//
// Returns the total number of rows deleted in this pass (across both modes).
func (r *Retention) Run(ctx context.Context) (int64, error) {
	var total int64

	// Age-based trim.
	if r.config.MaxAge > 0 {
		before := time.Now().Add(-r.config.MaxAge)
		n, err := r.store.DeleteBefore(ctx, before)
		if err != nil {
			return total, err
		}
		total += n
		if n > 0 {
			log.Printf("audit retention: deleted %d rows older than %s", n, r.config.MaxAge)
		}
	}

	// Row-count-cap trim.
	if r.config.MaxRows > 0 {
		count, err := r.store.CountRows(ctx)
		if err != nil {
			return total, err
		}
		surplus := count - r.config.MaxRows
		if surplus > 0 {
			n, err := r.store.DeleteOldest(ctx, surplus)
			if err != nil {
				return total, err
			}
			total += n
			if n > 0 {
				log.Printf("audit retention: deleted %d oldest rows to enforce cap of %d", n, r.config.MaxRows)
			}
		}
	}

	return total, nil
}
