package store

import (
	"database/sql"
	"errors"
	"fmt"
)

// InsertDeployHistory appends one row to the per-workload deploy ledger.
// Callers (the deployer choke point) treat this as best-effort: a failure
// here must never fail an otherwise-successful deploy. Error is expected to
// be a fixed, secret-free marker — never the raw source error.
func (s *Store) InsertDeployHistory(e DeployHistoryEntry) (DeployHistoryEntry, error) {
	if e.StartedAt == "" {
		e.StartedAt = Now()
	}
	if e.FinishedAt == "" {
		e.FinishedAt = Now()
	}
	res, err := s.db.Exec(
		`INSERT INTO deploy_history
		   (workload_id, source_kind, reference, reason, triggered_by,
		    note, outcome, error, started_at, finished_at)
		 VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
		e.WorkloadID, e.SourceKind, e.Reference, e.Reason, e.TriggeredBy,
		e.Note, e.Outcome, e.Error, e.StartedAt, e.FinishedAt,
	)
	if err != nil {
		return DeployHistoryEntry{}, fmt.Errorf("insert deploy history: %w", err)
	}
	id, err := res.LastInsertId()
	if err != nil {
		return DeployHistoryEntry{}, fmt.Errorf("get deploy history id: %w", err)
	}
	e.ID = id
	return e, nil
}

// ListDeployHistory returns a workload's ledger newest-first. limit/offset
// are assumed pre-clamped by the API layer; a non-positive limit falls back
// to a sane default so a bad query can't return the whole table.
func (s *Store) ListDeployHistory(workloadID string, limit, offset int) ([]DeployHistoryEntry, error) {
	if limit <= 0 {
		limit = 50
	}
	if offset < 0 {
		offset = 0
	}
	rows, err := s.db.Query(
		`SELECT id, workload_id, source_kind, reference, reason, triggered_by,
		        note, outcome, error, started_at, finished_at
		 FROM deploy_history
		 WHERE workload_id = ?
		 ORDER BY id DESC
		 LIMIT ? OFFSET ?`,
		workloadID, limit, offset,
	)
	if err != nil {
		return nil, fmt.Errorf("query deploy history: %w", err)
	}
	defer rows.Close()

	out := make([]DeployHistoryEntry, 0, limit)
	for rows.Next() {
		var e DeployHistoryEntry
		if err := rows.Scan(
			&e.ID, &e.WorkloadID, &e.SourceKind, &e.Reference, &e.Reason,
			&e.TriggeredBy, &e.Note, &e.Outcome, &e.Error, &e.StartedAt, &e.FinishedAt,
		); err != nil {
			return nil, fmt.Errorf("scan deploy history: %w", err)
		}
		out = append(out, e)
	}
	return out, rows.Err()
}

// GetDeployHistory fetches one ledger row by id, or ErrNotFound. The
// rollback handler uses this to resolve the pinned reference to replay.
func (s *Store) GetDeployHistory(id int64) (DeployHistoryEntry, error) {
	row := s.db.QueryRow(
		`SELECT id, workload_id, source_kind, reference, reason, triggered_by,
		        note, outcome, error, started_at, finished_at
		 FROM deploy_history WHERE id = ?`, id,
	)
	var e DeployHistoryEntry
	err := row.Scan(
		&e.ID, &e.WorkloadID, &e.SourceKind, &e.Reference, &e.Reason,
		&e.TriggeredBy, &e.Note, &e.Outcome, &e.Error, &e.StartedAt, &e.FinishedAt,
	)
	if errors.Is(err, sql.ErrNoRows) {
		return DeployHistoryEntry{}, fmt.Errorf("deploy history %d: %w", id, ErrNotFound)
	}
	if err != nil {
		return DeployHistoryEntry{}, fmt.Errorf("scan deploy history: %w", err)
	}
	return e, nil
}

// PruneDeployHistory keeps only the newest `keep` rows for a workload,
// deleting older ones. Bounds unbounded growth on hot workloads. Best-
// effort and id-monotonic (newer rows always have larger ids), so it
// deletes everything below the keep-th id. A non-positive keep is treated
// as "keep a sane default" rather than "delete everything".
func (s *Store) PruneDeployHistory(workloadID string, keep int) error {
	if keep <= 0 {
		keep = 50
	}
	_, err := s.db.Exec(
		`DELETE FROM deploy_history
		 WHERE workload_id = ?
		   AND id NOT IN (
		       SELECT id FROM deploy_history
		       WHERE workload_id = ?
		       ORDER BY id DESC
		       LIMIT ?
		   )`,
		workloadID, workloadID, keep,
	)
	if err != nil {
		return fmt.Errorf("prune deploy history: %w", err)
	}
	return nil
}
