package store

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

	"github.com/google/uuid"
)

const bindingColumns = `id, workload_id, trigger_id, binding_config,
	enabled, sort_order, created_at, updated_at`

func scanBinding(s rowScanner) (WorkloadTriggerBinding, error) {
	var b WorkloadTriggerBinding
	var enabled int
	if err := s.Scan(&b.ID, &b.WorkloadID, &b.TriggerID, &b.BindingConfig,
		&enabled, &b.SortOrder, &b.CreatedAt, &b.UpdatedAt); err != nil {
		return WorkloadTriggerBinding{}, err
	}
	b.Enabled = enabled != 0
	return b, nil
}

// CreateBinding inserts a binding row. The (workload_id, trigger_id) pair
// must be unique — re-binding an existing pair is an UpdateBinding call,
// not an insert.
func (s *Store) CreateBinding(b WorkloadTriggerBinding) (WorkloadTriggerBinding, error) {
	if b.ID == "" {
		b.ID = uuid.New().String()
	}
	if b.BindingConfig == "" {
		b.BindingConfig = "{}"
	}
	b.CreatedAt = Now()
	b.UpdatedAt = b.CreatedAt
	_, err := s.db.Exec(
		`INSERT INTO workload_trigger_bindings (`+bindingColumns+`)
		 VALUES (?, ?, ?, ?, ?, ?, ?, ?)`,
		b.ID, b.WorkloadID, b.TriggerID, b.BindingConfig,
		BoolToInt(b.Enabled), b.SortOrder, b.CreatedAt, b.UpdatedAt,
	)
	if err != nil {
		return WorkloadTriggerBinding{}, fmt.Errorf("insert binding: %w", translateSQLError(err))
	}
	return b, nil
}

// GetBindingByID returns one binding by its primary key.
func (s *Store) GetBindingByID(id string) (WorkloadTriggerBinding, error) {
	b, err := scanBinding(s.db.QueryRow(
		`SELECT `+bindingColumns+` FROM workload_trigger_bindings WHERE id = ?`, id,
	))
	if errors.Is(err, sql.ErrNoRows) {
		return WorkloadTriggerBinding{}, fmt.Errorf("binding %s: %w", id, ErrNotFound)
	}
	if err != nil {
		return WorkloadTriggerBinding{}, fmt.Errorf("query binding: %w", err)
	}
	return b, nil
}

// ListBindingsForWorkload returns every trigger bound to a workload,
// ordered by sort_order then created_at for stable display.
func (s *Store) ListBindingsForWorkload(workloadID string) ([]WorkloadTriggerBinding, error) {
	rows, err := s.db.Query(
		`SELECT `+bindingColumns+` FROM workload_trigger_bindings
		 WHERE workload_id = ? ORDER BY sort_order, created_at`,
		workloadID,
	)
	if err != nil {
		return nil, fmt.Errorf("query bindings for workload: %w", err)
	}
	defer rows.Close()

	out := []WorkloadTriggerBinding{}
	for rows.Next() {
		b, err := scanBinding(rows)
		if err != nil {
			return nil, fmt.Errorf("scan binding: %w", err)
		}
		out = append(out, b)
	}
	return out, rows.Err()
}

// ListBindingsForTrigger returns every workload bound to a trigger,
// ordered by sort_order. Used by the webhook fan-out path.
func (s *Store) ListBindingsForTrigger(triggerID string) ([]WorkloadTriggerBinding, error) {
	rows, err := s.db.Query(
		`SELECT `+bindingColumns+` FROM workload_trigger_bindings
		 WHERE trigger_id = ? ORDER BY sort_order, created_at`,
		triggerID,
	)
	if err != nil {
		return nil, fmt.Errorf("query bindings for trigger: %w", err)
	}
	defer rows.Close()

	out := []WorkloadTriggerBinding{}
	for rows.Next() {
		b, err := scanBinding(rows)
		if err != nil {
			return nil, fmt.Errorf("scan binding: %w", err)
		}
		out = append(out, b)
	}
	return out, rows.Err()
}

// GetBindingByPair returns the binding for an exact (workload, trigger)
// pair. ErrNotFound when missing.
func (s *Store) GetBindingByPair(workloadID, triggerID string) (WorkloadTriggerBinding, error) {
	b, err := scanBinding(s.db.QueryRow(
		`SELECT `+bindingColumns+` FROM workload_trigger_bindings
		 WHERE workload_id = ? AND trigger_id = ?`,
		workloadID, triggerID,
	))
	if errors.Is(err, sql.ErrNoRows) {
		return WorkloadTriggerBinding{}, ErrNotFound
	}
	if err != nil {
		return WorkloadTriggerBinding{}, fmt.Errorf("query binding by pair: %w", err)
	}
	return b, nil
}

// UpdateBinding updates the mutable fields of a binding (binding_config,
// enabled, sort_order). The (workload_id, trigger_id) pair is immutable
// — to re-target, delete and re-insert.
func (s *Store) UpdateBinding(b WorkloadTriggerBinding) error {
	b.UpdatedAt = Now()
	if b.BindingConfig == "" {
		b.BindingConfig = "{}"
	}
	result, err := s.db.Exec(
		`UPDATE workload_trigger_bindings
		 SET binding_config=?, enabled=?, sort_order=?, updated_at=?
		 WHERE id=?`,
		b.BindingConfig, BoolToInt(b.Enabled), b.SortOrder, b.UpdatedAt, b.ID,
	)
	if err != nil {
		return fmt.Errorf("update binding: %w", err)
	}
	n, _ := result.RowsAffected()
	if n == 0 {
		return fmt.Errorf("binding %s: %w", b.ID, ErrNotFound)
	}
	return nil
}

// DeleteBinding removes a binding row.
func (s *Store) DeleteBinding(id string) error {
	result, err := s.db.Exec(
		`DELETE FROM workload_trigger_bindings WHERE id = ?`, id,
	)
	if err != nil {
		return fmt.Errorf("delete binding: %w", err)
	}
	n, _ := result.RowsAffected()
	if n == 0 {
		return fmt.Errorf("binding %s: %w", id, ErrNotFound)
	}
	return nil
}

// DeleteBindingsForWorkload removes every binding for a workload.
// Idempotent — returns nil even if no rows existed.
func (s *Store) DeleteBindingsForWorkload(workloadID string) error {
	_, err := s.db.Exec(
		`DELETE FROM workload_trigger_bindings WHERE workload_id = ?`,
		workloadID,
	)
	if err != nil {
		return fmt.Errorf("delete bindings for workload: %w", err)
	}
	return nil
}

// BindingWithNames carries a binding plus the human names of its
// workload + trigger so the API listing endpoints render in one
// round-trip instead of N+1 lookups.
type BindingWithNames struct {
	WorkloadTriggerBinding
	WorkloadName string
	TriggerKind  string
	TriggerName  string
}

// ListBindingsForTriggerWithNames is the join-aware variant of
// ListBindingsForTrigger that also surfaces the workload's name.
func (s *Store) ListBindingsForTriggerWithNames(triggerID string) ([]BindingWithNames, error) {
	rows, err := s.db.Query(
		`SELECT b.id, b.workload_id, b.trigger_id, b.binding_config,
			b.enabled, b.sort_order, b.created_at, b.updated_at,
			COALESCE(w.name, '')
		 FROM workload_trigger_bindings b
		 LEFT JOIN workloads w ON w.id = b.workload_id
		 WHERE b.trigger_id = ?
		 ORDER BY b.sort_order, b.created_at`,
		triggerID,
	)
	if err != nil {
		return nil, fmt.Errorf("query bindings+names for trigger: %w", err)
	}
	defer rows.Close()
	out := []BindingWithNames{}
	for rows.Next() {
		var b WorkloadTriggerBinding
		var enabled int
		var workloadName string
		if err := rows.Scan(&b.ID, &b.WorkloadID, &b.TriggerID, &b.BindingConfig,
			&enabled, &b.SortOrder, &b.CreatedAt, &b.UpdatedAt, &workloadName); err != nil {
			return nil, fmt.Errorf("scan binding+name: %w", err)
		}
		b.Enabled = enabled != 0
		out = append(out, BindingWithNames{WorkloadTriggerBinding: b, WorkloadName: workloadName})
	}
	return out, rows.Err()
}

// ListBindingsForWorkloadWithNames is the join-aware variant of
// ListBindingsForWorkload that also surfaces the trigger's kind + name.
func (s *Store) ListBindingsForWorkloadWithNames(workloadID string) ([]BindingWithNames, error) {
	rows, err := s.db.Query(
		`SELECT b.id, b.workload_id, b.trigger_id, b.binding_config,
			b.enabled, b.sort_order, b.created_at, b.updated_at,
			COALESCE(t.kind, ''), COALESCE(t.name, '')
		 FROM workload_trigger_bindings b
		 LEFT JOIN triggers t ON t.id = b.trigger_id
		 WHERE b.workload_id = ?
		 ORDER BY b.sort_order, b.created_at`,
		workloadID,
	)
	if err != nil {
		return nil, fmt.Errorf("query bindings+names for workload: %w", err)
	}
	defer rows.Close()
	out := []BindingWithNames{}
	for rows.Next() {
		var b WorkloadTriggerBinding
		var enabled int
		var kind, name string
		if err := rows.Scan(&b.ID, &b.WorkloadID, &b.TriggerID, &b.BindingConfig,
			&enabled, &b.SortOrder, &b.CreatedAt, &b.UpdatedAt, &kind, &name); err != nil {
			return nil, fmt.Errorf("scan binding+trigger names: %w", err)
		}
		b.Enabled = enabled != 0
		out = append(out, BindingWithNames{
			WorkloadTriggerBinding: b,
			TriggerKind:            kind,
			TriggerName:            name,
		})
	}
	return out, rows.Err()
}

// CountBindingsForTrigger returns the number of bindings a trigger has.
// Used by the UI to decide whether deleting a trigger is safe.
func (s *Store) CountBindingsForTrigger(triggerID string) (int, error) {
	var n int
	err := s.db.QueryRow(
		`SELECT COUNT(*) FROM workload_trigger_bindings WHERE trigger_id = ?`,
		triggerID,
	).Scan(&n)
	if err != nil {
		return 0, fmt.Errorf("count bindings for trigger: %w", err)
	}
	return n, nil
}
