package tag

import (
	"context"
	"database/sql"
)

type Row struct {
	ID   int64
	Name string
}

// QFindOrCreate finds a tag by name or creates it if it doesn't exist.
func QFindOrCreate(ctx context.Context, db *sql.DB, name string) (Row, error) {
	query := `
		INSERT INTO tag (name)
		VALUES ($1)
		ON CONFLICT (name) DO UPDATE SET name = EXCLUDED.name
		RETURNING id, name
	`

	var row Row
	err := db.QueryRowContext(ctx, query, name).Scan(&row.ID, &row.Name)
	return row, err
}

// QFindByName finds a tag by name.
func QFindByName(ctx context.Context, db *sql.DB, name string) (*Row, error) {
	query := `SELECT id, name FROM tag WHERE name = $1`

	var row Row
	err := db.QueryRowContext(ctx, query, name).Scan(&row.ID, &row.Name)
	if err == sql.ErrNoRows {
		return nil, nil
	}
	if err != nil {
		return nil, err
	}
	return &row, nil
}

// QList returns all tags ordered by name.
func QList(ctx context.Context, db *sql.DB) ([]Row, error) {
	query := `SELECT id, name FROM tag ORDER BY name ASC`

	rows, err := db.QueryContext(ctx, query)
	if err != nil {
		return nil, err
	}
	defer rows.Close()

	var tags []Row
	for rows.Next() {
		var row Row
		if err := rows.Scan(&row.ID, &row.Name); err != nil {
			return nil, err
		}
		tags = append(tags, row)
	}
	return tags, rows.Err()
}

// QSuggest returns tags matching a prefix.
func QSuggest(ctx context.Context, db *sql.DB, prefix string, limit int) ([]Row, error) {
	query := `
		SELECT id, name FROM tag
		WHERE name ILIKE $1 || '%'
		ORDER BY name ASC
		LIMIT $2
	`

	rows, err := db.QueryContext(ctx, query, prefix, limit)
	if err != nil {
		return nil, err
	}
	defer rows.Close()

	var tags []Row
	for rows.Next() {
		var row Row
		if err := rows.Scan(&row.ID, &row.Name); err != nil {
			return nil, err
		}
		tags = append(tags, row)
	}
	return tags, rows.Err()
}

// QTagsForItem returns all tags for an item.
func QTagsForItem(ctx context.Context, db *sql.DB, itemID int64) ([]Row, error) {
	query := `
		SELECT t.id, t.name
		FROM tag t
		JOIN item_tag it ON t.id = it.tag_id
		WHERE it.item_id = $1
		ORDER BY t.name ASC
	`

	rows, err := db.QueryContext(ctx, query, itemID)
	if err != nil {
		return nil, err
	}
	defer rows.Close()

	var tags []Row
	for rows.Next() {
		var row Row
		if err := rows.Scan(&row.ID, &row.Name); err != nil {
			return nil, err
		}
		tags = append(tags, row)
	}
	return tags, rows.Err()
}

// QAddTagToItem adds a tag to an item.
func QAddTagToItem(ctx context.Context, db *sql.DB, itemID, tagID int64) error {
	query := `
		INSERT INTO item_tag (item_id, tag_id)
		VALUES ($1, $2)
		ON CONFLICT DO NOTHING
	`
	_, err := db.ExecContext(ctx, query, itemID, tagID)
	return err
}

// QRemoveTagFromItem removes a tag from an item.
func QRemoveTagFromItem(ctx context.Context, db *sql.DB, itemID, tagID int64) error {
	query := `DELETE FROM item_tag WHERE item_id = $1 AND tag_id = $2`
	_, err := db.ExecContext(ctx, query, itemID, tagID)
	return err
}

// QSetTagsForItem replaces all tags for an item with the given tag names.
func QSetTagsForItem(ctx context.Context, db *sql.DB, itemID int64, tagNames []string) error {
	// Delete existing tags
	_, err := db.ExecContext(ctx, `DELETE FROM item_tag WHERE item_id = $1`, itemID)
	if err != nil {
		return err
	}

	// Add new tags
	for _, name := range tagNames {
		tag, err := QFindOrCreate(ctx, db, name)
		if err != nil {
			return err
		}
		if err := QAddTagToItem(ctx, db, itemID, tag.ID); err != nil {
			return err
		}
	}
	return nil
}
