//go:build linux && cgo && !agent

package cluster

// The code below was generated by lxd-generate - DO NOT EDIT!

import (
	"context"
	"database/sql"
	"errors"
	"fmt"
	"net/http"
	"strings"

	"github.com/canonical/lxd/lxd/db/query"
	"github.com/canonical/lxd/shared/api"
)

var _ = api.ServerEnvironment{}

var identityObjects = RegisterStmt(`
SELECT identities.id, identities.auth_method, identities.type, identities.identifier, identities.name, identities.metadata
  FROM identities
  ORDER BY identities.auth_method, identities.identifier
`)

var identityObjectsByID = RegisterStmt(`
SELECT identities.id, identities.auth_method, identities.type, identities.identifier, identities.name, identities.metadata
  FROM identities
  WHERE ( identities.id = ? )
  ORDER BY identities.auth_method, identities.identifier
`)

var identityObjectsByAuthMethod = RegisterStmt(`
SELECT identities.id, identities.auth_method, identities.type, identities.identifier, identities.name, identities.metadata
  FROM identities
  WHERE ( identities.auth_method = ? )
  ORDER BY identities.auth_method, identities.identifier
`)

var identityObjectsByAuthMethodAndType = RegisterStmt(`
SELECT identities.id, identities.auth_method, identities.type, identities.identifier, identities.name, identities.metadata
  FROM identities
  WHERE ( identities.auth_method = ? AND identities.type = ? )
  ORDER BY identities.auth_method, identities.identifier
`)

var identityObjectsByAuthMethodAndIdentifier = RegisterStmt(`
SELECT identities.id, identities.auth_method, identities.type, identities.identifier, identities.name, identities.metadata
  FROM identities
  WHERE ( identities.auth_method = ? AND identities.identifier = ? )
  ORDER BY identities.auth_method, identities.identifier
`)

var identityObjectsByAuthMethodAndName = RegisterStmt(`
SELECT identities.id, identities.auth_method, identities.type, identities.identifier, identities.name, identities.metadata
  FROM identities
  WHERE ( identities.auth_method = ? AND identities.name = ? )
  ORDER BY identities.auth_method, identities.identifier
`)

var identityObjectsByType = RegisterStmt(`
SELECT identities.id, identities.auth_method, identities.type, identities.identifier, identities.name, identities.metadata
  FROM identities
  WHERE ( identities.type = ? )
  ORDER BY identities.auth_method, identities.identifier
`)

var identityID = RegisterStmt(`
SELECT identities.id FROM identities
  WHERE identities.auth_method = ? AND identities.identifier = ?
`)

var identityCreate = RegisterStmt(`
INSERT INTO identities (auth_method, type, identifier, name, metadata)
  VALUES (?, ?, ?, ?, ?)
`)

var identityDeleteByAuthMethodAndIdentifier = RegisterStmt(`
DELETE FROM identities WHERE auth_method = ? AND identifier = ?
`)

var identityDeleteByNameAndType = RegisterStmt(`
DELETE FROM identities WHERE name = ? AND type = ?
`)

var identityUpdate = RegisterStmt(`
UPDATE identities
  SET auth_method = ?, type = ?, identifier = ?, name = ?, metadata = ?
 WHERE id = ?
`)

// identityColumns returns a string of column names to be used with a SELECT statement for the entity.
// Use this function when building statements to retrieve database entries matching the Identity entity.
func identityColumns() string {
	return "identity.id, identity.auth_method, identity.type, identity.identifier, identity.name, identity.metadata"
}

// getIdentitys can be used to run handwritten sql.Stmts to return a slice of objects.
func getIdentitys(ctx context.Context, stmt *sql.Stmt, args ...any) ([]Identity, error) {
	objects := make([]Identity, 0)

	dest := func(scan func(dest ...any) error) error {
		i := Identity{}
		err := scan(&i.ID, &i.AuthMethod, &i.Type, &i.Identifier, &i.Name, &i.Metadata)
		if err != nil {
			return err
		}

		objects = append(objects, i)

		return nil
	}

	err := query.SelectObjects(ctx, stmt, dest, args...)
	if err != nil {
		return nil, fmt.Errorf("Failed to fetch from \"identity\" table: %w", err)
	}

	return objects, nil
}

// getIdentitysRaw can be used to run handwritten query strings to return a slice of objects.
func getIdentitysRaw(ctx context.Context, tx *sql.Tx, sql string, args ...any) ([]Identity, error) {
	objects := make([]Identity, 0)

	dest := func(scan func(dest ...any) error) error {
		i := Identity{}
		err := scan(&i.ID, &i.AuthMethod, &i.Type, &i.Identifier, &i.Name, &i.Metadata)
		if err != nil {
			return err
		}

		objects = append(objects, i)

		return nil
	}

	err := query.Scan(ctx, tx, sql, dest, args...)
	if err != nil {
		return nil, fmt.Errorf("Failed to fetch from \"identity\" table: %w", err)
	}

	return objects, nil
}

// GetIdentitys returns all available identitys.
// generator: identity GetMany
func GetIdentitys(ctx context.Context, tx *sql.Tx, filters ...IdentityFilter) ([]Identity, error) {
	var err error

	// Result slice.
	objects := make([]Identity, 0)

	// Pick the prepared statement and arguments to use based on active criteria.
	var sqlStmt *sql.Stmt
	args := []any{}
	queryParts := [2]string{}

	if len(filters) == 0 {
		sqlStmt, err = Stmt(tx, identityObjects)
		if err != nil {
			return nil, fmt.Errorf("Failed to get \"identityObjects\" prepared statement: %w", err)
		}
	}

	for i, filter := range filters {
		if filter.AuthMethod != nil && filter.Type != nil && filter.ID == nil && filter.Identifier == nil && filter.Name == nil {
			args = append(args, []any{filter.AuthMethod, filter.Type}...)
			if len(filters) == 1 {
				sqlStmt, err = Stmt(tx, identityObjectsByAuthMethodAndType)
				if err != nil {
					return nil, fmt.Errorf("Failed to get \"identityObjectsByAuthMethodAndType\" prepared statement: %w", err)
				}

				break
			}

			query, err := StmtString(identityObjectsByAuthMethodAndType)
			if err != nil {
				return nil, fmt.Errorf("Failed to get \"identityObjects\" prepared statement: %w", err)
			}

			parts := strings.SplitN(query, "ORDER BY", 2)
			if i == 0 {
				copy(queryParts[:], parts)
				continue
			}

			_, where, _ := strings.Cut(parts[0], "WHERE")
			queryParts[0] += "OR" + where
		} else if filter.AuthMethod != nil && filter.Name != nil && filter.ID == nil && filter.Type == nil && filter.Identifier == nil {
			args = append(args, []any{filter.AuthMethod, filter.Name}...)
			if len(filters) == 1 {
				sqlStmt, err = Stmt(tx, identityObjectsByAuthMethodAndName)
				if err != nil {
					return nil, fmt.Errorf("Failed to get \"identityObjectsByAuthMethodAndName\" prepared statement: %w", err)
				}

				break
			}

			query, err := StmtString(identityObjectsByAuthMethodAndName)
			if err != nil {
				return nil, fmt.Errorf("Failed to get \"identityObjects\" prepared statement: %w", err)
			}

			parts := strings.SplitN(query, "ORDER BY", 2)
			if i == 0 {
				copy(queryParts[:], parts)
				continue
			}

			_, where, _ := strings.Cut(parts[0], "WHERE")
			queryParts[0] += "OR" + where
		} else if filter.AuthMethod != nil && filter.Identifier != nil && filter.ID == nil && filter.Type == nil && filter.Name == nil {
			args = append(args, []any{filter.AuthMethod, filter.Identifier}...)
			if len(filters) == 1 {
				sqlStmt, err = Stmt(tx, identityObjectsByAuthMethodAndIdentifier)
				if err != nil {
					return nil, fmt.Errorf("Failed to get \"identityObjectsByAuthMethodAndIdentifier\" prepared statement: %w", err)
				}

				break
			}

			query, err := StmtString(identityObjectsByAuthMethodAndIdentifier)
			if err != nil {
				return nil, fmt.Errorf("Failed to get \"identityObjects\" prepared statement: %w", err)
			}

			parts := strings.SplitN(query, "ORDER BY", 2)
			if i == 0 {
				copy(queryParts[:], parts)
				continue
			}

			_, where, _ := strings.Cut(parts[0], "WHERE")
			queryParts[0] += "OR" + where
		} else if filter.Type != nil && filter.ID == nil && filter.AuthMethod == nil && filter.Identifier == nil && filter.Name == nil {
			args = append(args, []any{filter.Type}...)
			if len(filters) == 1 {
				sqlStmt, err = Stmt(tx, identityObjectsByType)
				if err != nil {
					return nil, fmt.Errorf("Failed to get \"identityObjectsByType\" prepared statement: %w", err)
				}

				break
			}

			query, err := StmtString(identityObjectsByType)
			if err != nil {
				return nil, fmt.Errorf("Failed to get \"identityObjects\" prepared statement: %w", err)
			}

			parts := strings.SplitN(query, "ORDER BY", 2)
			if i == 0 {
				copy(queryParts[:], parts)
				continue
			}

			_, where, _ := strings.Cut(parts[0], "WHERE")
			queryParts[0] += "OR" + where
		} else if filter.ID != nil && filter.AuthMethod == nil && filter.Type == nil && filter.Identifier == nil && filter.Name == nil {
			args = append(args, []any{filter.ID}...)
			if len(filters) == 1 {
				sqlStmt, err = Stmt(tx, identityObjectsByID)
				if err != nil {
					return nil, fmt.Errorf("Failed to get \"identityObjectsByID\" prepared statement: %w", err)
				}

				break
			}

			query, err := StmtString(identityObjectsByID)
			if err != nil {
				return nil, fmt.Errorf("Failed to get \"identityObjects\" prepared statement: %w", err)
			}

			parts := strings.SplitN(query, "ORDER BY", 2)
			if i == 0 {
				copy(queryParts[:], parts)
				continue
			}

			_, where, _ := strings.Cut(parts[0], "WHERE")
			queryParts[0] += "OR" + where
		} else if filter.AuthMethod != nil && filter.ID == nil && filter.Type == nil && filter.Identifier == nil && filter.Name == nil {
			args = append(args, []any{filter.AuthMethod}...)
			if len(filters) == 1 {
				sqlStmt, err = Stmt(tx, identityObjectsByAuthMethod)
				if err != nil {
					return nil, fmt.Errorf("Failed to get \"identityObjectsByAuthMethod\" prepared statement: %w", err)
				}

				break
			}

			query, err := StmtString(identityObjectsByAuthMethod)
			if err != nil {
				return nil, fmt.Errorf("Failed to get \"identityObjects\" prepared statement: %w", err)
			}

			parts := strings.SplitN(query, "ORDER BY", 2)
			if i == 0 {
				copy(queryParts[:], parts)
				continue
			}

			_, where, _ := strings.Cut(parts[0], "WHERE")
			queryParts[0] += "OR" + where
		} else if filter.ID == nil && filter.AuthMethod == nil && filter.Type == nil && filter.Identifier == nil && filter.Name == nil {
			return nil, errors.New("Cannot filter on empty IdentityFilter")
		} else {
			return nil, errors.New("No statement exists for the given Filter")
		}
	}

	// Select.
	if sqlStmt != nil {
		objects, err = getIdentitys(ctx, sqlStmt, args...)
	} else {
		queryStr := strings.Join(queryParts[:], "ORDER BY")
		objects, err = getIdentitysRaw(ctx, tx, queryStr, args...)
	}

	if err != nil {
		return nil, fmt.Errorf("Failed to fetch from \"identity\" table: %w", err)
	}

	return objects, nil
}

// GetIdentity returns the identity with the given key.
// generator: identity GetOne
func GetIdentity(ctx context.Context, tx *sql.Tx, authMethod AuthMethod, identifier string) (*Identity, error) {
	filter := IdentityFilter{}
	filter.AuthMethod = &authMethod
	filter.Identifier = &identifier

	objects, err := GetIdentitys(ctx, tx, filter)
	if err != nil {
		return nil, fmt.Errorf("Failed to fetch from \"identity\" table: %w", err)
	}

	switch len(objects) {
	case 0:
		return nil, api.StatusErrorf(http.StatusNotFound, "Identity not found")
	case 1:
		return &objects[0], nil
	default:
		return nil, errors.New("More than one \"identity\" entry matches")
	}
}

// GetIdentityID return the ID of the identity with the given key.
// generator: identity ID
func GetIdentityID(ctx context.Context, tx *sql.Tx, authMethod AuthMethod, identifier string) (int64, error) {
	stmt, err := Stmt(tx, identityID)
	if err != nil {
		return -1, fmt.Errorf("Failed to get \"identityID\" prepared statement: %w", err)
	}

	row := stmt.QueryRowContext(ctx, authMethod, identifier)
	var id int64
	err = row.Scan(&id)
	if err != nil {
		if errors.Is(err, sql.ErrNoRows) {
			return -1, api.StatusErrorf(http.StatusNotFound, "Identity not found")
		}

		return -1, fmt.Errorf("Failed to get \"identity\" ID: %w", err)
	}

	return id, nil
}

// CreateIdentity adds a new identity to the database.
// generator: identity Create
func CreateIdentity(ctx context.Context, tx *sql.Tx, object Identity) (int64, error) {
	args := make([]any, 5)

	// Populate the statement arguments.
	args[0] = object.AuthMethod
	args[1] = object.Type
	args[2] = object.Identifier
	args[3] = object.Name
	args[4] = object.Metadata

	// Prepared statement to use.
	stmt, err := Stmt(tx, identityCreate)
	if err != nil {
		return -1, fmt.Errorf("Failed to get \"identityCreate\" prepared statement: %w", err)
	}

	// Execute the statement.
	result, err := stmt.ExecContext(ctx, args...)
	if err != nil {
		if query.IsConflictErr(err) {
			return -1, api.NewStatusError(http.StatusConflict, "This \"identity\" entry already exists")
		}

		return -1, fmt.Errorf("Failed to create \"identity\" entry: %w", err)
	}

	id, err := result.LastInsertId()
	if err != nil {
		return -1, fmt.Errorf("Failed to fetch \"identity\" entry ID: %w", err)
	}

	return id, nil
}

// DeleteIdentity deletes the identity matching the given key parameters.
// generator: identity DeleteOne-by-AuthMethod-and-Identifier
func DeleteIdentity(ctx context.Context, tx *sql.Tx, authMethod AuthMethod, identifier string) error {
	stmt, err := Stmt(tx, identityDeleteByAuthMethodAndIdentifier)
	if err != nil {
		return fmt.Errorf("Failed to get \"identityDeleteByAuthMethodAndIdentifier\" prepared statement: %w", err)
	}

	result, err := stmt.ExecContext(ctx, authMethod, identifier)
	if err != nil {
		return fmt.Errorf("Delete \"identity\": %w", err)
	}

	n, err := result.RowsAffected()
	if err != nil {
		return fmt.Errorf("Fetch affected rows: %w", err)
	}

	if n == 0 {
		return api.StatusErrorf(http.StatusNotFound, "Identity not found")
	} else if n > 1 {
		return fmt.Errorf("Query deleted %d Identity rows instead of 1", n)
	}

	return nil
}

// DeleteIdentitys deletes the identity matching the given key parameters.
// generator: identity DeleteMany-by-Name-and-Type
func DeleteIdentitys(ctx context.Context, tx *sql.Tx, name string, identityType IdentityType) error {
	stmt, err := Stmt(tx, identityDeleteByNameAndType)
	if err != nil {
		return fmt.Errorf("Failed to get \"identityDeleteByNameAndType\" prepared statement: %w", err)
	}

	result, err := stmt.ExecContext(ctx, name, identityType)
	if err != nil {
		return fmt.Errorf("Delete \"identity\": %w", err)
	}

	_, err = result.RowsAffected()
	if err != nil {
		return fmt.Errorf("Fetch affected rows: %w", err)
	}

	return nil
}

// UpdateIdentity updates the identity matching the given key parameters.
// generator: identity Update
func UpdateIdentity(ctx context.Context, tx *sql.Tx, authMethod AuthMethod, identifier string, object Identity) error {
	id, err := GetIdentityID(ctx, tx, authMethod, identifier)
	if err != nil {
		return err
	}

	stmt, err := Stmt(tx, identityUpdate)
	if err != nil {
		return fmt.Errorf("Failed to get \"identityUpdate\" prepared statement: %w", err)
	}

	result, err := stmt.ExecContext(ctx, object.AuthMethod, object.Type, object.Identifier, object.Name, object.Metadata, id)
	if err != nil {
		if query.IsConflictErr(err) {
			return api.NewStatusError(http.StatusConflict, "A \"identity\" entry already exists with these properties")
		}

		return fmt.Errorf("Update \"identity\" entry failed: %w", err)
	}

	n, err := result.RowsAffected()
	if err != nil {
		return fmt.Errorf("Fetch affected rows: %w", err)
	}

	if n != 1 {
		return fmt.Errorf("Query updated %d rows instead of 1", n)
	}

	return nil
}
