package store

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

// CreateMetricAlertRule inserts a new rule row after validating its
// metric/comparator/severity enums and rejecting negative cooldowns.
func (s *Store) CreateMetricAlertRule(r MetricAlertRule) (MetricAlertRule, error) {
	if err := validateMetricAlertRule(r); err != nil {
		return MetricAlertRule{}, err
	}
	now := Now()
	r.CreatedAt = now
	r.UpdatedAt = now
	res, err := s.db.Exec(
		`INSERT INTO metric_alert_rules
		   (workload_id, name, metric, comparator, threshold, severity,
		    cooldown_seconds, enabled, created_at, updated_at)
		 VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
		r.WorkloadID, r.Name, r.Metric, r.Comparator, r.Threshold, r.Severity,
		r.CooldownSeconds, boolToInt(r.Enabled), r.CreatedAt, r.UpdatedAt,
	)
	if err != nil {
		return MetricAlertRule{}, fmt.Errorf("insert metric alert rule: %w", err)
	}
	id, err := res.LastInsertId()
	if err != nil {
		return MetricAlertRule{}, fmt.Errorf("get metric alert rule id: %w", err)
	}
	r.ID = id
	return r, nil
}

// ListMetricAlertRules returns every rule, ordered by id for stable UI
// rendering.
func (s *Store) ListMetricAlertRules() ([]MetricAlertRule, error) {
	return s.queryMetricAlertRules(
		`SELECT id, workload_id, name, metric, comparator, threshold, severity,
		        cooldown_seconds, enabled, created_at, updated_at
		 FROM metric_alert_rules ORDER BY id`,
	)
}

// ListMetricAlertRulesByWorkload returns rules that apply to the given
// workload: rows explicitly scoped to it plus global rows (workload_id
// = ""). Useful for the workload detail page.
func (s *Store) ListMetricAlertRulesByWorkload(workloadID string) ([]MetricAlertRule, error) {
	return s.queryMetricAlertRules(
		`SELECT id, workload_id, name, metric, comparator, threshold, severity,
		        cooldown_seconds, enabled, created_at, updated_at
		 FROM metric_alert_rules WHERE workload_id = ? OR workload_id = '' ORDER BY id`,
		workloadID,
	)
}

// GetMetricAlertRule fetches one rule by id or returns ErrNotFound.
func (s *Store) GetMetricAlertRule(id int64) (MetricAlertRule, error) {
	row := s.db.QueryRow(
		`SELECT id, workload_id, name, metric, comparator, threshold, severity,
		        cooldown_seconds, enabled, created_at, updated_at
		 FROM metric_alert_rules WHERE id = ?`, id,
	)
	r, err := scanMetricAlertRuleRow(row)
	if errors.Is(err, sql.ErrNoRows) {
		return MetricAlertRule{}, fmt.Errorf("metric alert rule %d: %w", id, ErrNotFound)
	}
	if err != nil {
		return MetricAlertRule{}, fmt.Errorf("query metric alert rule: %w", err)
	}
	return r, nil
}

// UpdateMetricAlertRule overwrites the editable columns of a rule row.
// id and workload_id are immutable on update — change the scope of a
// rule by deleting + recreating, mirroring the log-scan store.
func (s *Store) UpdateMetricAlertRule(r MetricAlertRule) (MetricAlertRule, error) {
	if r.ID == 0 {
		return MetricAlertRule{}, fmt.Errorf("metric alert rule: id is required for update")
	}
	if err := validateMetricAlertRule(r); err != nil {
		return MetricAlertRule{}, err
	}
	r.UpdatedAt = Now()
	res, err := s.db.Exec(
		`UPDATE metric_alert_rules
		    SET name = ?, metric = ?, comparator = ?, threshold = ?, severity = ?,
		        cooldown_seconds = ?, enabled = ?, updated_at = ?
		  WHERE id = ?`,
		r.Name, r.Metric, r.Comparator, r.Threshold, r.Severity,
		r.CooldownSeconds, boolToInt(r.Enabled), r.UpdatedAt, r.ID,
	)
	if err != nil {
		return MetricAlertRule{}, fmt.Errorf("update metric alert rule: %w", err)
	}
	n, _ := res.RowsAffected()
	if n == 0 {
		return MetricAlertRule{}, fmt.Errorf("metric alert rule %d: %w", r.ID, ErrNotFound)
	}
	return s.GetMetricAlertRule(r.ID)
}

// DeleteMetricAlertRule removes a rule by id, returning ErrNotFound when
// no row matched.
func (s *Store) DeleteMetricAlertRule(id int64) error {
	res, err := s.db.Exec(`DELETE FROM metric_alert_rules WHERE id = ?`, id)
	if err != nil {
		return fmt.Errorf("delete metric alert rule: %w", err)
	}
	n, _ := res.RowsAffected()
	if n == 0 {
		return fmt.Errorf("metric alert rule %d: %w", id, ErrNotFound)
	}
	return nil
}

func (s *Store) queryMetricAlertRules(query string, args ...any) ([]MetricAlertRule, error) {
	rows, err := s.db.Query(query, args...)
	if err != nil {
		return nil, fmt.Errorf("query metric alert rules: %w", err)
	}
	defer rows.Close()
	out := []MetricAlertRule{}
	for rows.Next() {
		r, err := scanMetricAlertRuleRows(rows)
		if err != nil {
			return nil, err
		}
		out = append(out, r)
	}
	return out, rows.Err()
}

func scanMetricAlertRuleRows(rows *sql.Rows) (MetricAlertRule, error) {
	var r MetricAlertRule
	var enabled int
	if err := rows.Scan(
		&r.ID, &r.WorkloadID, &r.Name, &r.Metric, &r.Comparator, &r.Threshold, &r.Severity,
		&r.CooldownSeconds, &enabled, &r.CreatedAt, &r.UpdatedAt,
	); err != nil {
		return MetricAlertRule{}, fmt.Errorf("scan metric alert rule: %w", err)
	}
	r.Enabled = enabled != 0
	return r, nil
}

func scanMetricAlertRuleRow(row *sql.Row) (MetricAlertRule, error) {
	var r MetricAlertRule
	var enabled int
	if err := row.Scan(
		&r.ID, &r.WorkloadID, &r.Name, &r.Metric, &r.Comparator, &r.Threshold, &r.Severity,
		&r.CooldownSeconds, &enabled, &r.CreatedAt, &r.UpdatedAt,
	); err != nil {
		return MetricAlertRule{}, err
	}
	r.Enabled = enabled != 0
	return r, nil
}

// validateMetricAlertRule enforces the per-row invariants: a non-empty
// name, a known metric/comparator, a valid severity (blank allowed so
// the caller can default it), and a non-negative cooldown.
func validateMetricAlertRule(r MetricAlertRule) error {
	if strings.TrimSpace(r.Name) == "" {
		return fmt.Errorf("metric alert rule: name is required")
	}
	switch r.Metric {
	case MetricCPUPercent, MetricMemoryPercent, MetricMemoryBytes:
	default:
		return fmt.Errorf("metric alert rule: invalid metric %q", r.Metric)
	}
	switch r.Comparator {
	case MetricComparatorGT, MetricComparatorLT:
	default:
		return fmt.Errorf("metric alert rule: invalid comparator %q", r.Comparator)
	}
	switch r.Severity {
	case LogScanSeverityInfo, LogScanSeverityWarn, LogScanSeverityError:
	case "":
		// Default applied at the caller; allow blank.
	default:
		return fmt.Errorf("metric alert rule: invalid severity %q", r.Severity)
	}
	if r.CooldownSeconds < 0 {
		return fmt.Errorf("metric alert rule: cooldown_seconds must be >= 0")
	}
	return nil
}
