// Package tqm is the Phase 9 tqm integration. The Command Center does not
// reimplement tqm's rule evaluation; it captures tqm's run outputs (via an
// operator-installed cron wrapper that posts to a webhook), surfaces
// recent runs from audit_log, and offers a dry-run shim.
package tqm

import (
	"context"
	"database/sql"
	"encoding/json"
	"errors"
	"fmt"
	"os/exec"
	"strings"
	"time"
)

// RunSummary is the JSON shape the operator-installed cron wrapper posts.
type RunSummary struct {
	RunID         string   `json:"run_id"`
	StartedAt     int64    `json:"started_at"`
	FinishedAt    int64    `json:"finished_at"`
	DurationMS    int64    `json:"duration_ms"`
	TorrentsRemoved []string `json:"torrents_removed,omitempty"`
	TorrentsTagged  []string `json:"torrents_tagged,omitempty"`
	TorrentsUpdated []string `json:"torrents_updated,omitempty"`
	Errors          []string `json:"errors,omitempty"`
}

// Recorder writes one tqm run into audit_log. Phase 9 reuses audit_log
// rather than introducing a new table — the operator-facing queries are
// already there from Phase 0 / Phase 15.
type Recorder struct {
	db *sql.DB
}

// NewRecorder constructs a Recorder.
func NewRecorder(db *sql.DB) *Recorder { return &Recorder{db: db} }

// Record persists one run summary.
func (r *Recorder) Record(ctx context.Context, s RunSummary) error {
	details, _ := json.Marshal(s)
	_, err := r.db.ExecContext(ctx, `
		INSERT INTO audit_log(timestamp, actor, action, target_type, target_id, details_json)
		VALUES (?, 'tqm', 'tqm_run', 'tqm_run', ?, ?)
	`, time.Now().Unix(), s.RunID, string(details))
	return err
}

// RecentRuns reads back recent runs from audit_log, decoding details_json.
func (r *Recorder) RecentRuns(ctx context.Context, limit int) ([]RunSummary, error) {
	if limit <= 0 || limit > 500 {
		limit = 50
	}
	rows, err := r.db.QueryContext(ctx, `
		SELECT details_json FROM audit_log
		WHERE action = 'tqm_run' AND details_json IS NOT NULL
		ORDER BY timestamp DESC LIMIT ?
	`, limit)
	if err != nil {
		return nil, err
	}
	defer rows.Close()
	var out []RunSummary
	for rows.Next() {
		var raw string
		if err := rows.Scan(&raw); err != nil {
			return nil, err
		}
		var s RunSummary
		_ = json.Unmarshal([]byte(raw), &s)
		out = append(out, s)
	}
	return out, rows.Err()
}

// DryRunCommand runs the tqm binary with --dry-run + the operator's config
// directory. The exec is bounded by a context timeout and the output is
// returned as a string for operator inspection. Phase 9 deliberately does
// not parse the output into structured form — tqm's text output varies
// across versions; the operator wants the raw output for sanity checking.
func DryRunCommand(ctx context.Context, tqmBinary, configDir string, args ...string) (string, error) {
	if tqmBinary == "" {
		tqmBinary = "tqm"
	}
	allArgs := append([]string{"--config", configDir, "--dry-run"}, args...)
	if _, err := exec.LookPath(tqmBinary); err != nil {
		return "", fmt.Errorf("tqm: binary not found in PATH: %w", err)
	}
	dctx, cancel := contextWithTimeout(ctx, 60*time.Second)
	defer cancel()
	cmd := exec.CommandContext(dctx, tqmBinary, allArgs...)
	out, err := cmd.CombinedOutput()
	if err != nil {
		return strings.TrimSpace(string(out)), fmt.Errorf("tqm: %w", err)
	}
	return strings.TrimSpace(string(out)), nil
}

// contextWithTimeout is a tiny wrapper that avoids importing context.WithTimeout
// twice in this file (and matches the convenience naming used elsewhere).
func contextWithTimeout(parent context.Context, d time.Duration) (context.Context, context.CancelFunc) {
	if parent == nil {
		return nil, func() {}
	}
	if _, ok := parent.Deadline(); ok {
		// Already bounded; respect the caller's bound.
		return parent, func() {}
	}
	return _ctxWithTimeout(parent, d)
}

// Ensure RunSummary has stable JSON encoding.
var _ = errors.New
