package store

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

	"github.com/google/uuid"
)

const appColumns = `id, name, description, created_at, updated_at`

func scanApp(scanner interface{ Scan(...any) error }) (App, error) {
	var a App
	err := scanner.Scan(&a.ID, &a.Name, &a.Description, &a.CreatedAt, &a.UpdatedAt)
	return a, err
}

// CreateApp inserts a new app row. Names must be unique.
func (s *Store) CreateApp(a App) (App, error) {
	if a.ID == "" {
		a.ID = uuid.New().String()
	}
	a.CreatedAt = Now()
	a.UpdatedAt = a.CreatedAt

	_, err := s.db.Exec(
		`INSERT INTO apps (`+appColumns+`) VALUES (?, ?, ?, ?, ?)`,
		a.ID, a.Name, a.Description, a.CreatedAt, a.UpdatedAt,
	)
	if err != nil {
		return App{}, fmt.Errorf("insert app: %w", err)
	}
	return a, nil
}

// GetAppByID returns an app by ID.
func (s *Store) GetAppByID(id string) (App, error) {
	a, err := scanApp(s.db.QueryRow(
		`SELECT `+appColumns+` FROM apps WHERE id = ?`, id,
	))
	if errors.Is(err, sql.ErrNoRows) {
		return App{}, fmt.Errorf("app %s: %w", id, ErrNotFound)
	}
	if err != nil {
		return App{}, fmt.Errorf("query app: %w", err)
	}
	return a, nil
}

// ListApps returns all apps, ordered by name.
func (s *Store) ListApps() ([]App, error) {
	rows, err := s.db.Query(
		`SELECT ` + appColumns + ` FROM apps ORDER BY name`,
	)
	if err != nil {
		return nil, fmt.Errorf("query apps: %w", err)
	}
	defer rows.Close()

	out := []App{}
	for rows.Next() {
		a, err := scanApp(rows)
		if err != nil {
			return nil, fmt.Errorf("scan app: %w", err)
		}
		out = append(out, a)
	}
	return out, rows.Err()
}

// UpdateApp updates the mutable fields (name, description) of an app row.
func (s *Store) UpdateApp(a App) error {
	a.UpdatedAt = Now()
	result, err := s.db.Exec(
		`UPDATE apps SET name=?, description=?, updated_at=? WHERE id=?`,
		a.Name, a.Description, a.UpdatedAt, a.ID,
	)
	if err != nil {
		return fmt.Errorf("update app: %w", err)
	}
	n, err := result.RowsAffected()
	if err != nil {
		return fmt.Errorf("rows affected: %w", err)
	}
	if n == 0 {
		return fmt.Errorf("app %s: %w", a.ID, ErrNotFound)
	}
	return nil
}

// DeleteApp removes an app and clears its app_id from any workloads.
// Workloads survive — they just become unassigned.
func (s *Store) DeleteApp(id string) error {
	tx, err := s.db.Begin()
	if err != nil {
		return fmt.Errorf("begin delete app: %w", err)
	}
	defer tx.Rollback()

	if _, err := tx.Exec(`UPDATE workloads SET app_id = '' WHERE app_id = ?`, id); err != nil {
		return fmt.Errorf("clear app_id on workloads: %w", err)
	}
	result, err := tx.Exec(`DELETE FROM apps WHERE id = ?`, id)
	if err != nil {
		return fmt.Errorf("delete app: %w", err)
	}
	n, err := result.RowsAffected()
	if err != nil {
		return fmt.Errorf("rows affected: %w", err)
	}
	if n == 0 {
		return fmt.Errorf("app %s: %w", id, ErrNotFound)
	}
	return tx.Commit()
}
