package organization

import (
	"context"

	"github.com/tidbcloud/AgentTeam/internal/coredb"
)

type mutationExecer interface {
	Exec(context.Context, string, ...any) (coredb.Result, error)
}

type mutationQueryRower interface {
	mutationExecer
	QueryRow(context.Context, string, ...any) coredb.Row
}

type mutationQueryExecer interface {
	mutationExecer
	Query(context.Context, string, ...any) (coredb.Rows, error)
}

// execPortableUpdateThenInsert keeps backend differences out of upper layers by
// checking the logical key first, then reconciling a concurrent duplicate insert with a portable
// follow-up update.
func execPortableUpdateThenInsert(
	ctx context.Context,
	execer mutationQueryRower,
	existsQuery string,
	existsArgs []any,
	updateQuery string,
	updateArgs []any,
	insertQuery string,
	insertArgs []any,
) error {
	exists, err := rowExists(ctx, execer, existsQuery, existsArgs)
	if err != nil {
		return err
	}
	if exists {
		result, err := execer.Exec(ctx, updateQuery, updateArgs...)
		if err != nil {
			return err
		}
		if result != nil && result.RowsAffected() > 0 {
			return nil
		}

		// A zero-row update can mean either "the row already had these values" or
		// "the row disappeared after the existence check"; only the latter needs a reinsert.
		exists, err = rowExists(ctx, execer, existsQuery, existsArgs)
		if err != nil {
			return err
		}
		if exists {
			return nil
		}
	}

	if _, err := execer.Exec(ctx, insertQuery, insertArgs...); err == nil {
		return nil
	} else if !coredb.IsUniqueViolation(err) {
		return err
	} else {
		duplicateErr := err
		exists, err = rowExists(ctx, execer, existsQuery, existsArgs)
		if err != nil {
			return err
		}
		if !exists {
			return duplicateErr
		}
		_, err = execer.Exec(ctx, updateQuery, updateArgs...)
		return err
	}
}

func queryStringSetThenExecUpdate(
	ctx context.Context,
	queryer mutationQueryExecer,
	selectQuery string,
	selectArgs []any,
	updateQuery string,
	updateArgs []any,
) (map[string]struct{}, error) {
	values, err := queryStringSet(ctx, queryer, selectQuery, selectArgs)
	if err != nil {
		return nil, err
	}
	if len(values) == 0 {
		return map[string]struct{}{}, nil
	}
	if _, err := queryer.Exec(ctx, updateQuery, updateArgs...); err != nil {
		return nil, err
	}
	return values, nil
}

func queryStringSet(
	ctx context.Context,
	queryer mutationQueryExecer,
	query string,
	args []any,
) (map[string]struct{}, error) {
	rows, err := queryer.Query(ctx, query, args...)
	if err != nil {
		return nil, err
	}
	defer rows.Close()

	values := make(map[string]struct{})
	for rows.Next() {
		var value string
		if err := rows.Scan(&value); err != nil {
			return nil, err
		}
		values[value] = struct{}{}
	}
	if err := rows.Err(); err != nil {
		return nil, err
	}
	return values, nil
}

func rowExists(
	ctx context.Context,
	queryer mutationQueryRower,
	query string,
	args []any,
) (bool, error) {
	var marker int
	if err := queryer.QueryRow(ctx, query, args...).Scan(&marker); err != nil {
		if coredb.IsNotFound(err) {
			return false, nil
		}
		return false, err
	}
	return true, nil
}
