package postgresql

import (
	"context"
	"database/sql"
	"encoding/json"
	"time"

	"github.com/golang-migrate/migrate/v4"
	"github.com/huandu/go-sqlbuilder"
	"github.com/jackc/pgx/v5"
	"github.com/jackc/pgx/v5/pgxpool"
	"github.com/sagernet/sing-box/common/byteformats"
	"github.com/sagernet/sing-box/service/manager/constant"
)

var squadFilters, nodeFilters, userFilters, bandwidthLimiterFilters, connectionLimiterFilters, trafficLimiterFilters, rateLimiterFilters map[string]Filter

type PostgreSQLRepository struct {
	db  *pgxpool.Pool
	ctx context.Context
}

func NewPostgreSQLRepository(ctx context.Context, dsn string) (*PostgreSQLRepository, error) {
	db, err := sql.Open("postgres", dsn)
	if err != nil {
		return nil, err
	}
	defer db.Close()
	if err := Migrate(db); err != nil && err != migrate.ErrNoChange {
		return nil, err
	}
	pool, err := pgxpool.New(ctx, dsn)
	if err != nil {
		return nil, err
	}
	return &PostgreSQLRepository{db: pool, ctx: ctx}, nil
}

func (r *PostgreSQLRepository) CreateSquad(squad constant.SquadCreate) (constant.Squad, error) {
	var s constant.Squad
	now := time.Now()
	err := r.db.QueryRow(
		r.ctx, `
		INSERT INTO squads
		(
			name,
			created_at,
			updated_at
		)
		VALUES ($1, $2, $3)
		RETURNING
			id,
			name,
			created_at,
			updated_at
	`,
		squad.Name,
		now,
		now,
	).Scan(
		&s.ID,
		&s.Name,
		&s.CreatedAt,
		&s.UpdatedAt,
	)
	return s, err
}

func (r *PostgreSQLRepository) GetSquads(filters map[string][]string) ([]constant.Squad, error) {
	sb := sqlbuilder.PostgreSQL.NewSelectBuilder().
		Select(
			"id",
			"name",
			"created_at",
			"updated_at",
		).
		From("squads")
	for k, v := range filters {
		if f, ok := squadFilters[k]; ok {
			if err := f(sb, v); err != nil {
				return nil, err
			}
		}
	}
	sql, args := sb.Build()
	rows, err := r.db.Query(r.ctx, sql, args...)
	if err != nil {
		return nil, err
	}
	defer rows.Close()
	var result []constant.Squad
	for rows.Next() {
		var squad constant.Squad
		if err := rows.Scan(
			&squad.ID,
			&squad.Name,
			&squad.CreatedAt,
			&squad.UpdatedAt,
		); err != nil {
			return nil, err
		}
		result = append(result, squad)
	}
	return result, rows.Err()
}

func (r *PostgreSQLRepository) GetSquadsCount(filters map[string][]string) (int, error) {
	sb := sqlbuilder.PostgreSQL.NewSelectBuilder().
		Select("COUNT(*)").
		From("squads")
	for k, v := range filters {
		if f, ok := squadFilters[k]; ok {
			if err := f(sb, v); err != nil {
				return 0, err
			}
		}
	}
	sql, args := sb.Build()
	var count int
	err := r.db.QueryRow(r.ctx, sql, args...).Scan(&count)
	return count, err
}

func (r *PostgreSQLRepository) GetSquad(id int) (constant.Squad, error) {
	var s constant.Squad
	err := r.db.QueryRow(r.ctx, `
		SELECT
			id,
			name,
			created_at,
			updated_at
		FROM squads
		WHERE id=$1
	`, id).Scan(
		&s.ID,
		&s.Name,
		&s.CreatedAt,
		&s.UpdatedAt,
	)
	return s, err
}

func (r *PostgreSQLRepository) UpdateSquad(id int, squad constant.SquadUpdate) (constant.Squad, error) {
	var s constant.Squad
	err := r.db.QueryRow(
		r.ctx, `
		UPDATE squads
		SET
			name=$1,
			updated_at=$2
		WHERE id=$3
		RETURNING
			id,
			name,
			created_at,
			updated_at
	`,
		squad.Name,
		time.Now(),
		id,
	).Scan(
		&s.ID,
		&s.Name,
		&s.CreatedAt,
		&s.UpdatedAt,
	)
	return s, err
}

func (r *PostgreSQLRepository) DeleteSquad(id int) (constant.DeletedSquad, error) {
	var result constant.DeletedSquad
	tx, err := r.db.Begin(r.ctx)
	if err != nil {
		return result, err
	}
	defer tx.Rollback(r.ctx)
	affectedNodeRows, err := tx.Query(r.ctx, `DELETE FROM node_to_squad WHERE squad_id=$1 RETURNING node_uuid`, id)
	if err != nil {
		return result, err
	}
	affectedNodeUUIDs := make([]string, 0)
	for affectedNodeRows.Next() {
		var uuid string
		if err = affectedNodeRows.Scan(&uuid); err != nil {
			affectedNodeRows.Close()
			return result, err
		}
		affectedNodeUUIDs = append(affectedNodeUUIDs, uuid)
	}
	affectedNodeRows.Close()
	if err = affectedNodeRows.Err(); err != nil {
		return result, err
	}
	for _, table := range []string{
		"user_to_squad",
		"connection_limiter_to_squad",
		"bandwidth_limiter_to_squad",
		"traffic_limiter_to_squad",
		"rate_limiter_to_squad",
	} {
		if _, err = tx.Exec(r.ctx, `DELETE FROM `+table+` WHERE squad_id=$1`, id); err != nil {
			return result, err
		}
	}
	err = tx.QueryRow(r.ctx, `
		DELETE FROM squads
		WHERE id=$1
		RETURNING
			id,
			name,
			created_at,
			updated_at
	`, id).Scan(
		&result.Squad.ID,
		&result.Squad.Name,
		&result.Squad.CreatedAt,
		&result.Squad.UpdatedAt,
	)
	if err != nil {
		return result, err
	}
	orphanedNodeRows, err := tx.Query(r.ctx, `
		DELETE FROM nodes
		WHERE NOT EXISTS (SELECT 1 FROM node_to_squad WHERE node_to_squad.node_uuid = nodes.uuid)
		RETURNING uuid
	`)
	if err != nil {
		return result, err
	}
	orphanedNodeUUIDs := make(map[string]struct{})
	for orphanedNodeRows.Next() {
		var uuid string
		if err = orphanedNodeRows.Scan(&uuid); err != nil {
			orphanedNodeRows.Close()
			return result, err
		}
		orphanedNodeUUIDs[uuid] = struct{}{}
		result.OrphanedNodeUUIDs = append(result.OrphanedNodeUUIDs, uuid)
	}
	orphanedNodeRows.Close()
	if err = orphanedNodeRows.Err(); err != nil {
		return result, err
	}
	for _, uuid := range affectedNodeUUIDs {
		if _, ok := orphanedNodeUUIDs[uuid]; !ok {
			result.SurvivingNodeUUIDs = append(result.SurvivingNodeUUIDs, uuid)
		}
	}
	if _, err = tx.Exec(r.ctx, `
		DELETE FROM users
		WHERE NOT EXISTS (SELECT 1 FROM user_to_squad WHERE user_to_squad.user_id = users.id)
	`); err != nil {
		return result, err
	}
	connRows, err := tx.Query(r.ctx, `
		DELETE FROM connection_limiters
		WHERE NOT EXISTS (SELECT 1 FROM connection_limiter_to_squad WHERE connection_limiter_to_squad.connection_limiter_id = connection_limiters.id)
		RETURNING id
	`)
	if err != nil {
		return result, err
	}
	for connRows.Next() {
		var lid int
		if err = connRows.Scan(&lid); err != nil {
			connRows.Close()
			return result, err
		}
		result.OrphanedConnectionLimiterIDs = append(result.OrphanedConnectionLimiterIDs, lid)
	}
	connRows.Close()
	if err = connRows.Err(); err != nil {
		return result, err
	}
	if _, err = tx.Exec(r.ctx, `
		DELETE FROM bandwidth_limiters
		WHERE NOT EXISTS (SELECT 1 FROM bandwidth_limiter_to_squad WHERE bandwidth_limiter_to_squad.bandwidth_limiter_id = bandwidth_limiters.id)
	`); err != nil {
		return result, err
	}
	trafficRows, err := tx.Query(r.ctx, `
		DELETE FROM traffic_limiters
		WHERE NOT EXISTS (SELECT 1 FROM traffic_limiter_to_squad WHERE traffic_limiter_to_squad.traffic_limiter_id = traffic_limiters.id)
		RETURNING id
	`)
	if err != nil {
		return result, err
	}
	for trafficRows.Next() {
		var lid int
		if err = trafficRows.Scan(&lid); err != nil {
			trafficRows.Close()
			return result, err
		}
		result.OrphanedTrafficLimiterIDs = append(result.OrphanedTrafficLimiterIDs, lid)
	}
	trafficRows.Close()
	if err = trafficRows.Err(); err != nil {
		return result, err
	}
	if _, err = tx.Exec(r.ctx, `
		DELETE FROM rate_limiters
		WHERE NOT EXISTS (SELECT 1 FROM rate_limiter_to_squad WHERE rate_limiter_to_squad.rate_limiter_id = rate_limiters.id)
	`); err != nil {
		return result, err
	}
	if err = tx.Commit(r.ctx); err != nil {
		return result, err
	}
	return result, nil
}

func (r *PostgreSQLRepository) CreateNode(node constant.NodeCreate) (constant.Node, error) {
	var n constant.Node
	tx, err := r.db.Begin(r.ctx)
	if err != nil {
		return n, err
	}
	defer tx.Rollback(r.ctx)
	now := time.Now()
	err = tx.QueryRow(
		r.ctx, `
		INSERT INTO nodes (
			uuid,
			name,
			created_at,
			updated_at
		)
		VALUES ($1, $2, $3, $4)
		RETURNING 
			uuid,
			name,
			created_at,
			updated_at
	`,
		node.UUID,
		node.Name,
		now,
		now,
	).Scan(
		&n.UUID,
		&n.Name,
		&n.CreatedAt,
		&n.UpdatedAt,
	)
	if err != nil {
		return n, err
	}
	rows := make([][]any, len(node.SquadIDs))
	for i, squadID := range node.SquadIDs {
		rows[i] = []any{node.UUID, squadID}
	}
	_, err = tx.CopyFrom(
		r.ctx,
		pgx.Identifier{"node_to_squad"},
		[]string{"node_uuid", "squad_id"},
		pgx.CopyFromRows(rows),
	)
	if err != nil {
		return n, err
	}
	err = tx.Commit(r.ctx)
	if err != nil {
		return n, err
	}
	return n, err
}

func (r *PostgreSQLRepository) GetNodes(filters map[string][]string) ([]constant.Node, error) {
	sb := sqlbuilder.PostgreSQL.NewSelectBuilder().
		Select(
			"uuid",
			"name",
			`ARRAY(
				SELECT squad_id
				FROM node_to_squad
				WHERE node_to_squad.node_uuid = nodes.uuid
			) as squad_ids`,
			"created_at",
			"updated_at",
		).
		From("nodes")
	for key, value := range filters {
		if filter, ok := nodeFilters[key]; ok {
			if err := filter(sb, value); err != nil {
				return nil, err
			}
		}
	}
	sql, args := sb.Build()
	rows, err := r.db.Query(r.ctx, sql, args...)
	if err != nil {
		return nil, err
	}
	defer rows.Close()
	var result []constant.Node
	for rows.Next() {
		var n constant.Node
		if err := rows.Scan(
			&n.UUID,
			&n.Name,
			&n.SquadIDs,
			&n.CreatedAt,
			&n.UpdatedAt,
		); err != nil {
			return nil, err
		}
		result = append(result, n)
	}
	return result, rows.Err()
}

func (r *PostgreSQLRepository) GetNodesCount(filters map[string][]string) (int, error) {
	sb := sqlbuilder.PostgreSQL.NewSelectBuilder().
		Select("COUNT(*)").
		From("nodes")
	for key, value := range filters {
		if filter, ok := nodeFilters[key]; ok {
			if err := filter(sb, value); err != nil {
				return 0, err
			}
		}
	}
	sql, args := sb.Build()
	var count int
	err := r.db.QueryRow(r.ctx, sql, args...).Scan(&count)
	return count, err
}

func (r *PostgreSQLRepository) GetNode(uuid string) (constant.Node, error) {
	var n constant.Node
	err := r.db.QueryRow(r.ctx, `
		SELECT 
			uuid,
			name, 
			ARRAY(
				SELECT squad_id
				FROM node_to_squad
				WHERE node_to_squad.node_uuid = nodes.uuid
			) as squad_ids,
			created_at,
			updated_at
		FROM nodes
		WHERE uuid = $1
	`, uuid).Scan(
		&n.UUID,
		&n.Name,
		&n.SquadIDs,
		&n.CreatedAt,
		&n.UpdatedAt,
	)
	if err != nil && err.Error() == "no rows in result set" {
		return n, constant.ErrNotFound
	}
	return n, err
}

func (r *PostgreSQLRepository) UpdateNode(uuid string, node constant.NodeUpdate) (constant.Node, error) {
	var n constant.Node
	err := r.db.QueryRow(
		r.ctx, `
		UPDATE nodes
		SET 
			name = $1,
			updated_at = $2
		WHERE uuid = $3
		RETURNING
			uuid,
			name,
			created_at,
			updated_at
	`,
		node.Name,
		time.Now(),
		uuid,
	).Scan(
		&n.UUID,
		&n.Name,
		&n.CreatedAt,
		&n.UpdatedAt,
	)
	return n, err
}

func (r *PostgreSQLRepository) DeleteNode(uuid string) (constant.Node, error) {
	var n constant.Node
	err := r.db.QueryRow(r.ctx, `
		DELETE FROM nodes
		WHERE uuid = $1
		RETURNING 
			uuid,
			name,
			created_at,
			updated_at
	`, uuid).Scan(
		&n.UUID,
		&n.Name,
		&n.CreatedAt,
		&n.UpdatedAt,
	)
	return n, err
}

func (r *PostgreSQLRepository) CreateUser(user constant.UserCreate) (constant.User, error) {
	var u constant.User
	tx, err := r.db.Begin(r.ctx)
	if err != nil {
		return u, err
	}
	defer tx.Rollback(r.ctx)
	now := time.Now()
	err = tx.QueryRow(
		r.ctx, `
		INSERT INTO users (
			username,
			inbound,
			type,
			uuid,
			password,
			secret,
			flow,
			alter_id,
			created_at,
			updated_at
		)
		VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)
		RETURNING 
			id,
			username,
			inbound,
			type,
			uuid,
			password,
			secret,
			flow,
			alter_id,
			created_at,
			updated_at
	`,
		user.Username,
		user.Inbound,
		user.Type,
		user.UUID,
		user.Password,
		user.Secret,
		user.Flow,
		user.AlterID,
		now,
		now,
	).Scan(
		&u.ID,
		&u.Username,
		&u.Inbound,
		&u.Type,
		&u.UUID,
		&u.Password,
		&u.Secret,
		&u.Flow,
		&u.AlterID,
		&u.CreatedAt,
		&u.UpdatedAt,
	)
	if err != nil {
		return u, err
	}
	rows := make([][]any, len(user.SquadIDs))
	for i, squadID := range user.SquadIDs {
		rows[i] = []any{u.ID, squadID}
	}
	_, err = tx.CopyFrom(
		r.ctx,
		pgx.Identifier{"user_to_squad"},
		[]string{"user_id", "squad_id"},
		pgx.CopyFromRows(rows),
	)
	if err != nil {
		return u, err
	}
	u.SquadIDs = user.SquadIDs
	err = tx.Commit(r.ctx)
	if err != nil {
		return u, err
	}
	return u, err
}

func (r *PostgreSQLRepository) GetUsers(filters map[string][]string) ([]constant.User, error) {
	sb := sqlbuilder.PostgreSQL.NewSelectBuilder().
		Select(
			"id",
			`ARRAY(
				SELECT squad_id
				FROM user_to_squad
				WHERE user_to_squad.user_id = users.id
			) as squad_ids`,
			"username",
			"inbound",
			"type",
			"uuid",
			"password",
			"secret",
			"flow",
			"alter_id",
			"created_at",
			"updated_at",
		).
		From("users")
	for key, value := range filters {
		if filter, ok := userFilters[key]; ok {
			if err := filter(sb, value); err != nil {
				return nil, err
			}
		}
	}
	sql, args := sb.Build()
	rows, err := r.db.Query(r.ctx, sql, args...)
	if err != nil {
		return nil, err
	}
	defer rows.Close()
	var result []constant.User
	for rows.Next() {
		var u constant.User
		if err := rows.Scan(
			&u.ID,
			&u.SquadIDs,
			&u.Username,
			&u.Inbound,
			&u.Type,
			&u.UUID,
			&u.Password,
			&u.Secret,
			&u.Flow,
			&u.AlterID,
			&u.CreatedAt,
			&u.UpdatedAt,
		); err != nil {
			return nil, err
		}
		result = append(result, u)
	}
	return result, rows.Err()
}

func (r *PostgreSQLRepository) GetUsersCount(filters map[string][]string) (int, error) {
	sb := sqlbuilder.PostgreSQL.NewSelectBuilder().
		Select("COUNT(*)").
		From("users")
	for key, value := range filters {
		if filter, ok := userFilters[key]; ok {
			if err := filter(sb, value); err != nil {
				return 0, err
			}
		}
	}
	sql, args := sb.Build()
	var count int
	err := r.db.QueryRow(r.ctx, sql, args...).Scan(&count)
	return count, err
}

func (r *PostgreSQLRepository) GetUser(id int) (constant.User, error) {
	var u constant.User
	err := r.db.QueryRow(r.ctx, `
		SELECT
			id,
			ARRAY(
				SELECT squad_id
				FROM user_to_squad
				WHERE user_to_squad.user_id = users.id
			) as squad_ids,
			username,
			inbound,
			type,
			uuid,
			password,
			secret,
			flow,
			alter_id,
			created_at,
			updated_at
		FROM users
		WHERE id = $1
	`, id).Scan(
		&u.ID,
		&u.SquadIDs,
		&u.Username,
		&u.Inbound,
		&u.Type,
		&u.UUID,
		&u.Password,
		&u.Secret,
		&u.Flow,
		&u.AlterID,
		&u.CreatedAt,
		&u.UpdatedAt,
	)
	return u, err
}

func (r *PostgreSQLRepository) UpdateUser(id int, user constant.UserUpdate) (constant.User, error) {
	var u constant.User
	err := r.db.QueryRow(
		r.ctx, `
		UPDATE users
		SET
			uuid = $1,
			password = $2,
			secret = $3,
			flow = $4,
			alter_id = $5,
			updated_at = $6
		WHERE id = $7
		RETURNING
			id,
			ARRAY(
				SELECT squad_id
				FROM user_to_squad
				WHERE user_to_squad.user_id = users.id
			) as squad_ids,
			username,
			inbound,
			type,
			uuid,
			password,
			secret,
			flow,
			alter_id,
			created_at,
			updated_at
	`,
		user.UUID,
		user.Password,
		user.Secret,
		user.Flow,
		user.AlterID,
		time.Now(),
		id,
	).Scan(
		&u.ID,
		&u.SquadIDs,
		&u.Username,
		&u.Inbound,
		&u.Type,
		&u.UUID,
		&u.Password,
		&u.Secret,
		&u.Flow,
		&u.AlterID,
		&u.CreatedAt,
		&u.UpdatedAt,
	)
	return u, err
}

func (r *PostgreSQLRepository) DeleteUser(id int) (constant.User, error) {
	var u constant.User
	err := r.db.QueryRow(r.ctx, `
		DELETE FROM users
		WHERE id = $1
		RETURNING
			id,
			ARRAY(
				SELECT squad_id
				FROM user_to_squad
				WHERE user_to_squad.user_id = users.id
			) as squad_ids,
			username,
			inbound,
			type,
			uuid,
			password,
			secret,
			flow,
			alter_id,
			created_at,
			updated_at
	`, id).Scan(
		&u.ID,
		&u.SquadIDs,
		&u.Username,
		&u.Inbound,
		&u.Type,
		&u.UUID,
		&u.Password,
		&u.Secret,
		&u.Flow,
		&u.AlterID,
		&u.CreatedAt,
		&u.UpdatedAt,
	)
	return u, err
}

func (r *PostgreSQLRepository) CreateConnectionLimiter(limiter constant.ConnectionLimiterCreate) (constant.ConnectionLimiter, error) {
	var cl constant.ConnectionLimiter
	tx, err := r.db.Begin(r.ctx)
	if err != nil {
		return cl, err
	}
	defer tx.Rollback(r.ctx)
	now := time.Now()
	err = tx.QueryRow(
		r.ctx, `
		INSERT INTO connection_limiters
		(
			username,
			outbound,
			strategy,
			connection_type,
			lock_type,
			count,
			created_at,
			updated_at
		)
		VALUES ($1, $2, $3, $4, $5, $6, $7, $8)
		RETURNING
			id,
			username,
			outbound,
			strategy,
			connection_type,
			lock_type,
			count,
			created_at,
			updated_at
	`,
		limiter.Username,
		limiter.Outbound,
		limiter.Strategy,
		limiter.ConnectionType,
		limiter.LockType,
		limiter.Count,
		now,
		now,
	).Scan(
		&cl.ID,
		&cl.Username,
		&cl.Outbound,
		&cl.Strategy,
		&cl.ConnectionType,
		&cl.LockType,
		&cl.Count,
		&cl.CreatedAt,
		&cl.UpdatedAt,
	)
	if err != nil {
		return cl, err
	}
	rows := make([][]any, len(limiter.SquadIDs))
	for i, squadID := range limiter.SquadIDs {
		rows[i] = []any{cl.ID, squadID}
	}
	_, err = tx.CopyFrom(
		r.ctx,
		pgx.Identifier{"connection_limiter_to_squad"},
		[]string{"connection_limiter_id", "squad_id"},
		pgx.CopyFromRows(rows),
	)
	if err != nil {
		return cl, err
	}
	cl.SquadIDs = limiter.SquadIDs
	err = tx.Commit(r.ctx)
	if err != nil {
		return cl, err
	}
	return cl, err
}

func (r *PostgreSQLRepository) GetConnectionLimiters(filters map[string][]string) ([]constant.ConnectionLimiter, error) {
	sb := sqlbuilder.PostgreSQL.NewSelectBuilder().
		Select(
			"id",
			`ARRAY(
				SELECT squad_id
				FROM connection_limiter_to_squad
				WHERE connection_limiter_to_squad.connection_limiter_id = connection_limiters.id
			) as squad_ids`,
			"username",
			"outbound",
			"strategy",
			"connection_type",
			"lock_type",
			"count",
			"created_at",
			"updated_at",
		).
		From("connection_limiters")
	for k, v := range filters {
		if f, ok := connectionLimiterFilters[k]; ok {
			if err := f(sb, v); err != nil {
				return nil, err
			}
		}
	}
	sql, args := sb.Build()
	rows, err := r.db.Query(r.ctx, sql, args...)
	if err != nil {
		return nil, err
	}
	defer rows.Close()
	var result []constant.ConnectionLimiter
	for rows.Next() {
		var cl constant.ConnectionLimiter
		if err := rows.Scan(
			&cl.ID,
			&cl.SquadIDs,
			&cl.Username,
			&cl.Outbound,
			&cl.Strategy,
			&cl.ConnectionType,
			&cl.LockType,
			&cl.Count,
			&cl.CreatedAt,
			&cl.UpdatedAt,
		); err != nil {
			return nil, err
		}
		result = append(result, cl)
	}

	return result, rows.Err()
}

func (r *PostgreSQLRepository) GetConnectionLimitersCount(filters map[string][]string) (int, error) {
	sb := sqlbuilder.PostgreSQL.NewSelectBuilder().
		Select("COUNT(*)").
		From("connection_limiters")

	for k, v := range filters {
		if f, ok := connectionLimiterFilters[k]; ok {
			if err := f(sb, v); err != nil {
				return 0, err
			}
		}
	}
	sql, args := sb.Build()
	var count int
	err := r.db.QueryRow(r.ctx, sql, args...).Scan(&count)
	return count, err
}

func (r *PostgreSQLRepository) GetConnectionLimiter(id int) (constant.ConnectionLimiter, error) {
	var cl constant.ConnectionLimiter
	err := r.db.QueryRow(r.ctx, `
		SELECT
			id,
			ARRAY(
				SELECT squad_id
				FROM connection_limiter_to_squad
				WHERE connection_limiter_to_squad.connection_limiter_id = connection_limiters.id
			) as squad_ids,
			username,
			outbound,
			strategy,
			connection_type,
			lock_type,
			count,
			created_at,
			updated_at
		FROM connection_limiters
		WHERE id=$1
	`, id).Scan(
		&cl.ID,
		&cl.SquadIDs,
		&cl.Username,
		&cl.Outbound,
		&cl.Strategy,
		&cl.ConnectionType,
		&cl.LockType,
		&cl.Count,
		&cl.CreatedAt,
		&cl.UpdatedAt,
	)
	return cl, err
}

func (r *PostgreSQLRepository) UpdateConnectionLimiter(id int, limiter constant.ConnectionLimiterUpdate) (constant.ConnectionLimiter, error) {
	var cl constant.ConnectionLimiter
	err := r.db.QueryRow(
		r.ctx, `
		UPDATE connection_limiters
		SET
			strategy=$1,
			connection_type=$2,
			lock_type=$3,
			count=$4,
			updated_at=$5
		WHERE id=$6
		RETURNING
			id,
			ARRAY(
				SELECT squad_id
				FROM connection_limiter_to_squad
				WHERE connection_limiter_to_squad.connection_limiter_id = connection_limiters.id
			) as squad_ids,
			username,
			outbound,
			strategy,
			connection_type,
			lock_type,
			count,
			created_at,
			updated_at
	`,
		limiter.Strategy,
		limiter.ConnectionType,
		limiter.LockType,
		limiter.Count,
		time.Now(),
		id,
	).Scan(
		&cl.ID,
		&cl.SquadIDs,
		&cl.Username,
		&cl.Outbound,
		&cl.Strategy,
		&cl.ConnectionType,
		&cl.LockType,
		&cl.Count,
		&cl.CreatedAt,
		&cl.UpdatedAt,
	)
	return cl, err
}

func (r *PostgreSQLRepository) DeleteConnectionLimiter(id int) (constant.ConnectionLimiter, error) {
	var cl constant.ConnectionLimiter
	err := r.db.QueryRow(r.ctx, `
		DELETE FROM connection_limiters
		WHERE id=$1
		RETURNING
			id,
			ARRAY(
				SELECT squad_id
				FROM connection_limiter_to_squad
				WHERE connection_limiter_to_squad.connection_limiter_id = connection_limiters.id
			) as squad_ids,
			username,
			outbound,
			strategy,
			connection_type,
			lock_type,
			count,
			created_at,
			updated_at
	`, id).Scan(
		&cl.ID,
		&cl.SquadIDs,
		&cl.Username,
		&cl.Outbound,
		&cl.Strategy,
		&cl.ConnectionType,
		&cl.LockType,
		&cl.Count,
		&cl.CreatedAt,
		&cl.UpdatedAt,
	)
	return cl, err
}

func (r *PostgreSQLRepository) CreateBandwidthLimiter(limiter constant.BandwidthLimiterCreate) (constant.BandwidthLimiter, error) {
	var bl constant.BandwidthLimiter
	tx, err := r.db.Begin(r.ctx)
	if err != nil {
		return bl, err
	}
	defer tx.Rollback(r.ctx)
	bytesSpeed, err := json.Marshal(limiter.Speed)
	if err != nil {
		return bl, err
	}
	raw := &byteformats.NetworkBytesCompat{}
	if err = raw.UnmarshalJSON(bytesSpeed); err != nil {
		return bl, err
	}
	flowKeysJSON, err := marshalStringSlice(limiter.FlowKeys)
	if err != nil {
		return bl, err
	}
	var flowKeys stringSliceJSON
	now := time.Now()
	err = tx.QueryRow(
		r.ctx, `
		INSERT INTO bandwidth_limiters
		(
			username,
			outbound,
			strategy,
			connection_type,
			mode,
			flow_keys,
			speed,
			raw_speed,
			created_at,
			updated_at
		)
		VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)
		RETURNING
			id,
			username,
			outbound,
			strategy,
			connection_type,
			mode,
			flow_keys,
			speed,
			raw_speed,
			created_at,
			updated_at
	`,
		limiter.Username,
		limiter.Outbound,
		limiter.Strategy,
		limiter.ConnectionType,
		limiter.Mode,
		flowKeysJSON,
		limiter.Speed,
		raw.Value(),
		now,
		now,
	).Scan(
		&bl.ID,
		&bl.Username,
		&bl.Outbound,
		&bl.Strategy,
		&bl.ConnectionType,
		&bl.Mode,
		&flowKeys,
		&bl.Speed,
		&bl.RawSpeed,
		&bl.CreatedAt,
		&bl.UpdatedAt,
	)
	if err != nil {
		return bl, err
	}
	bl.FlowKeys = []string(flowKeys)
	rows := make([][]any, len(limiter.SquadIDs))
	for i, squadID := range limiter.SquadIDs {
		rows[i] = []any{bl.ID, squadID}
	}
	_, err = tx.CopyFrom(
		r.ctx,
		pgx.Identifier{"bandwidth_limiter_to_squad"},
		[]string{"bandwidth_limiter_id", "squad_id"},
		pgx.CopyFromRows(rows),
	)
	if err != nil {
		return bl, err
	}
	bl.SquadIDs = limiter.SquadIDs
	err = tx.Commit(r.ctx)
	if err != nil {
		return bl, err
	}
	return bl, err
}

func (r *PostgreSQLRepository) GetBandwidthLimiters(filters map[string][]string) ([]constant.BandwidthLimiter, error) {
	sb := sqlbuilder.PostgreSQL.NewSelectBuilder().
		Select(
			"id",
			`ARRAY(
				SELECT squad_id
				FROM bandwidth_limiter_to_squad
				WHERE bandwidth_limiter_to_squad.bandwidth_limiter_id = bandwidth_limiters.id
			) as squad_ids`,
			"username",
			"outbound",
			"strategy",
			"connection_type",
			"mode",
			"flow_keys",
			"speed",
			"raw_speed",
			"created_at",
			"updated_at",
		).
		From("bandwidth_limiters")

	for k, v := range filters {
		if f, ok := bandwidthLimiterFilters[k]; ok {
			if err := f(sb, v); err != nil {
				return nil, err
			}
		}
	}
	sql, args := sb.Build()
	rows, err := r.db.Query(r.ctx, sql, args...)
	if err != nil {
		return nil, err
	}
	defer rows.Close()
	var result []constant.BandwidthLimiter
	for rows.Next() {
		var bl constant.BandwidthLimiter
		var flowKeys stringSliceJSON
		if err := rows.Scan(
			&bl.ID,
			&bl.SquadIDs,
			&bl.Username,
			&bl.Outbound,
			&bl.Strategy,
			&bl.ConnectionType,
			&bl.Mode,
			&flowKeys,
			&bl.Speed,
			&bl.RawSpeed,
			&bl.CreatedAt,
			&bl.UpdatedAt,
		); err != nil {
			return nil, err
		}
		bl.FlowKeys = []string(flowKeys)
		result = append(result, bl)
	}
	return result, rows.Err()
}

func (r *PostgreSQLRepository) GetBandwidthLimitersCount(filters map[string][]string) (int, error) {
	sb := sqlbuilder.PostgreSQL.NewSelectBuilder().
		Select("COUNT(*)").
		From("bandwidth_limiters")
	for k, v := range filters {
		if f, ok := bandwidthLimiterFilters[k]; ok {
			if err := f(sb, v); err != nil {
				return 0, err
			}
		}
	}
	sql, args := sb.Build()
	var count int
	err := r.db.QueryRow(r.ctx, sql, args...).Scan(&count)
	return count, err
}

func (r *PostgreSQLRepository) GetBandwidthLimiter(id int) (constant.BandwidthLimiter, error) {
	var bl constant.BandwidthLimiter
	var flowKeys stringSliceJSON
	err := r.db.QueryRow(r.ctx, `
		SELECT
			id,
			ARRAY(
				SELECT squad_id
				FROM bandwidth_limiter_to_squad
				WHERE bandwidth_limiter_to_squad.bandwidth_limiter_id = bandwidth_limiters.id
			) as squad_ids,
			username,
			outbound,
			strategy,
			connection_type,
			mode,
			flow_keys,
			speed,
			raw_speed,
			created_at,
			updated_at
		FROM bandwidth_limiters
		WHERE id=$1
	`, id).Scan(
		&bl.ID,
		&bl.SquadIDs,
		&bl.Username,
		&bl.Outbound,
		&bl.Strategy,
		&bl.ConnectionType,
		&bl.Mode,
		&flowKeys,
		&bl.Speed,
		&bl.RawSpeed,
		&bl.CreatedAt,
		&bl.UpdatedAt,
	)
	bl.FlowKeys = []string(flowKeys)
	return bl, err
}

func (r *PostgreSQLRepository) UpdateBandwidthLimiter(id int, limiter constant.BandwidthLimiterUpdate) (constant.BandwidthLimiter, error) {
	var bl constant.BandwidthLimiter
	var flowKeys stringSliceJSON
	bytesSpeed, err := json.Marshal(limiter.Speed)
	if err != nil {
		return bl, err
	}
	raw := &byteformats.NetworkBytesCompat{}
	if err = raw.UnmarshalJSON(bytesSpeed); err != nil {
		return bl, err
	}
	flowKeysJSON, err := marshalStringSlice(limiter.FlowKeys)
	if err != nil {
		return bl, err
	}
	err = r.db.QueryRow(
		r.ctx, `
		UPDATE bandwidth_limiters
		SET
			username=$1,
			outbound=$2,
			strategy=$3,
			connection_type=$4,
			mode=$5,
			flow_keys=$6,
			speed=$7,
			raw_speed=$8,
			updated_at=$9
		WHERE id=$10
		RETURNING
			id,
			ARRAY(
				SELECT squad_id
				FROM bandwidth_limiter_to_squad
				WHERE bandwidth_limiter_to_squad.bandwidth_limiter_id = bandwidth_limiters.id
			) as squad_ids,
			username,
			outbound,
			strategy,
			connection_type,
			mode,
			flow_keys,
			speed,
			raw_speed,
			created_at,
			updated_at
	`,
		limiter.Username,
		limiter.Outbound,
		limiter.Strategy,
		limiter.ConnectionType,
		limiter.Mode,
		flowKeysJSON,
		limiter.Speed,
		raw.Value(),
		time.Now(),
		id,
	).Scan(
		&bl.ID,
		&bl.SquadIDs,
		&bl.Username,
		&bl.Outbound,
		&bl.Strategy,
		&bl.ConnectionType,
		&bl.Mode,
		&flowKeys,
		&bl.Speed,
		&bl.RawSpeed,
		&bl.CreatedAt,
		&bl.UpdatedAt,
	)
	bl.FlowKeys = []string(flowKeys)
	return bl, err
}

func (r *PostgreSQLRepository) DeleteBandwidthLimiter(id int) (constant.BandwidthLimiter, error) {
	var bl constant.BandwidthLimiter
	var flowKeys stringSliceJSON
	err := r.db.QueryRow(r.ctx, `
		DELETE FROM bandwidth_limiters
		WHERE id=$1
		RETURNING
			id,
			ARRAY(
				SELECT squad_id
				FROM bandwidth_limiter_to_squad
				WHERE bandwidth_limiter_to_squad.bandwidth_limiter_id = bandwidth_limiters.id
			) as squad_ids,
			username,
			outbound,
			strategy,
			connection_type,
			mode,
			flow_keys,
			speed,
			raw_speed,
			created_at,
			updated_at
	`, id).Scan(
		&bl.ID,
		&bl.SquadIDs,
		&bl.Username,
		&bl.Outbound,
		&bl.Strategy,
		&bl.ConnectionType,
		&bl.Mode,
		&flowKeys,
		&bl.Speed,
		&bl.RawSpeed,
		&bl.CreatedAt,
		&bl.UpdatedAt,
	)
	bl.FlowKeys = []string(flowKeys)
	return bl, err
}

func (r *PostgreSQLRepository) CreateTrafficLimiter(limiter constant.TrafficLimiterCreate) (constant.TrafficLimiter, error) {
	var tl constant.TrafficLimiter
	tx, err := r.db.Begin(r.ctx)
	if err != nil {
		return tl, err
	}
	defer tx.Rollback(r.ctx)
	bytesQuota, err := json.Marshal(limiter.Quota)
	if err != nil {
		return tl, err
	}
	rawQuota := &byteformats.NetworkBytesCompat{}
	if err = rawQuota.UnmarshalJSON(bytesQuota); err != nil {
		return tl, err
	}
	now := time.Now()
	err = tx.QueryRow(
		r.ctx, `
		INSERT INTO traffic_limiters
		(
			username,
			outbound,
			strategy,
			mode,
			quota,
			raw_quota,
			created_at,
			updated_at
		)
		VALUES ($1, $2, $3, $4, $5, $6, $7, $8)
		RETURNING
			id,
			username,
			outbound,
			strategy,
			mode,
			raw_used,
			quota,
			raw_quota,
			CASE WHEN raw_quota = 0 THEN 0 ELSE LEAST(100, FLOOR(raw_used * 100.0 / raw_quota)::int) END AS usage,
			created_at,
			updated_at
	`,
		limiter.Username,
		limiter.Outbound,
		limiter.Strategy,
		limiter.Mode,
		limiter.Quota,
		rawQuota.Value(),
		now,
		now,
	).Scan(
		&tl.ID,
		&tl.Username,
		&tl.Outbound,
		&tl.Strategy,
		&tl.Mode,
		&tl.RawUsed,
		&tl.Quota,
		&tl.RawQuota,
		&tl.Usage,
		&tl.CreatedAt,
		&tl.UpdatedAt,
	)
	if err != nil {
		return tl, err
	}
	rows := make([][]any, len(limiter.SquadIDs))
	for i, squadID := range limiter.SquadIDs {
		rows[i] = []any{tl.ID, squadID}
	}
	_, err = tx.CopyFrom(
		r.ctx,
		pgx.Identifier{"traffic_limiter_to_squad"},
		[]string{"traffic_limiter_id", "squad_id"},
		pgx.CopyFromRows(rows),
	)
	if err != nil {
		return tl, err
	}
	tl.SquadIDs = limiter.SquadIDs
	err = tx.Commit(r.ctx)
	if err != nil {
		return tl, err
	}
	return tl, err
}

func (r *PostgreSQLRepository) GetTrafficLimiters(filters map[string][]string) ([]constant.TrafficLimiter, error) {
	sb := sqlbuilder.PostgreSQL.NewSelectBuilder().
		Select(
			"id",
			`ARRAY(
				SELECT squad_id
				FROM traffic_limiter_to_squad
				WHERE traffic_limiter_to_squad.traffic_limiter_id = traffic_limiters.id
			) as squad_ids`,
			"username",
			"outbound",
			"strategy",
			"mode",
			"raw_used",
			"quota",
			"raw_quota",
			"CASE WHEN raw_quota = 0 THEN 0 ELSE LEAST(100, FLOOR(raw_used * 100.0 / raw_quota)::int) END AS usage",
			"created_at",
			"updated_at",
		).
		From("traffic_limiters")

	for k, v := range filters {
		if f, ok := trafficLimiterFilters[k]; ok {
			if err := f(sb, v); err != nil {
				return nil, err
			}
		}
	}
	sql, args := sb.Build()
	rows, err := r.db.Query(r.ctx, sql, args...)
	if err != nil {
		return nil, err
	}
	defer rows.Close()
	var result []constant.TrafficLimiter
	for rows.Next() {
		var tl constant.TrafficLimiter
		if err := rows.Scan(
			&tl.ID,
			&tl.SquadIDs,
			&tl.Username,
			&tl.Outbound,
			&tl.Strategy,
			&tl.Mode,
			&tl.RawUsed,
			&tl.Quota,
			&tl.RawQuota,
			&tl.Usage,
			&tl.CreatedAt,
			&tl.UpdatedAt,
		); err != nil {
			return nil, err
		}
		result = append(result, tl)
	}
	return result, rows.Err()
}

func (r *PostgreSQLRepository) GetTrafficLimitersCount(filters map[string][]string) (int, error) {
	sb := sqlbuilder.PostgreSQL.NewSelectBuilder().
		Select("COUNT(*)").
		From("traffic_limiters")
	for k, v := range filters {
		if f, ok := trafficLimiterFilters[k]; ok {
			if err := f(sb, v); err != nil {
				return 0, err
			}
		}
	}
	sql, args := sb.Build()
	var count int
	err := r.db.QueryRow(r.ctx, sql, args...).Scan(&count)
	return count, err
}

func (r *PostgreSQLRepository) GetTrafficLimiter(id int) (constant.TrafficLimiter, error) {
	var tl constant.TrafficLimiter
	err := r.db.QueryRow(r.ctx, `
		SELECT
			id,
			ARRAY(
				SELECT squad_id
				FROM traffic_limiter_to_squad
				WHERE traffic_limiter_to_squad.traffic_limiter_id = traffic_limiters.id
			) as squad_ids,
			username,
			outbound,
			strategy,
			mode,
			raw_used,
			quota,
			raw_quota,
			CASE WHEN raw_quota = 0 THEN 0 ELSE LEAST(100, FLOOR(raw_used * 100.0 / raw_quota)::int) END AS usage,
			created_at,
			updated_at
		FROM traffic_limiters
		WHERE id=$1
	`, id).Scan(
		&tl.ID,
		&tl.SquadIDs,
		&tl.Username,
		&tl.Outbound,
		&tl.Strategy,
		&tl.Mode,
		&tl.RawUsed,
		&tl.Quota,
		&tl.RawQuota,
		&tl.Usage,
		&tl.CreatedAt,
		&tl.UpdatedAt,
	)
	return tl, err
}

func (r *PostgreSQLRepository) UpdateTrafficLimiter(id int, limiter constant.TrafficLimiterUpdate) (constant.TrafficLimiter, error) {
	var tl constant.TrafficLimiter
	bytesQuota, err := json.Marshal(limiter.Quota)
	if err != nil {
		return tl, err
	}
	rawQuota := &byteformats.NetworkBytesCompat{}
	if err = rawQuota.UnmarshalJSON(bytesQuota); err != nil {
		return tl, err
	}
	err = r.db.QueryRow(
		r.ctx, `
		UPDATE traffic_limiters
		SET
			username=$1,
			outbound=$2,
			strategy=$3,
			mode=$4,
			quota=$5,
			raw_quota=$6,
			updated_at=$7
		WHERE id=$8
		RETURNING
			id,
			ARRAY(
				SELECT squad_id
				FROM traffic_limiter_to_squad
				WHERE traffic_limiter_to_squad.traffic_limiter_id = traffic_limiters.id
			) as squad_ids,
			username,
			outbound,
			strategy,
			mode,
			raw_used,
			quota,
			raw_quota,
			CASE WHEN raw_quota = 0 THEN 0 ELSE LEAST(100, FLOOR(raw_used * 100.0 / raw_quota)::int) END AS usage,
			created_at,
			updated_at
	`,
		limiter.Username,
		limiter.Outbound,
		limiter.Strategy,
		limiter.Mode,
		limiter.Quota,
		rawQuota.Value(),
		time.Now(),
		id,
	).Scan(
		&tl.ID,
		&tl.SquadIDs,
		&tl.Username,
		&tl.Outbound,
		&tl.Strategy,
		&tl.Mode,
		&tl.RawUsed,
		&tl.Quota,
		&tl.RawQuota,
		&tl.Usage,
		&tl.CreatedAt,
		&tl.UpdatedAt,
	)
	return tl, err
}

func (r *PostgreSQLRepository) UpdateTrafficLimiterUsed(id int, current uint64) (constant.TrafficLimiter, error) {
	var tl constant.TrafficLimiter
	err := r.db.QueryRow(
		r.ctx, `
		UPDATE traffic_limiters
		SET
			raw_used=$1,
			updated_at=$2
		WHERE id=$3
		RETURNING
			id,
			ARRAY(
				SELECT squad_id
				FROM traffic_limiter_to_squad
				WHERE traffic_limiter_to_squad.traffic_limiter_id = traffic_limiters.id
			) as squad_ids,
			username,
			outbound,
			strategy,
			mode,
			raw_used,
			quota,
			raw_quota,
			CASE WHEN raw_quota = 0 THEN 0 ELSE LEAST(100, FLOOR(raw_used * 100.0 / raw_quota)::int) END AS usage,
			created_at,
			updated_at
	`,
		current,
		time.Now(),
		id,
	).Scan(
		&tl.ID,
		&tl.SquadIDs,
		&tl.Username,
		&tl.Outbound,
		&tl.Strategy,
		&tl.Mode,
		&tl.RawUsed,
		&tl.Quota,
		&tl.RawQuota,
		&tl.Usage,
		&tl.CreatedAt,
		&tl.UpdatedAt,
	)
	return tl, err
}

func (r *PostgreSQLRepository) DeleteTrafficLimiter(id int) (constant.TrafficLimiter, error) {
	var tl constant.TrafficLimiter
	err := r.db.QueryRow(r.ctx, `
		DELETE FROM traffic_limiters
		WHERE id=$1
		RETURNING
			id,
			ARRAY(
				SELECT squad_id
				FROM traffic_limiter_to_squad
				WHERE traffic_limiter_to_squad.traffic_limiter_id = traffic_limiters.id
			) as squad_ids,
			username,
			outbound,
			strategy,
			mode,
			raw_used,
			quota,
			raw_quota,
			CASE WHEN raw_quota = 0 THEN 0 ELSE LEAST(100, FLOOR(raw_used * 100.0 / raw_quota)::int) END AS usage,
			created_at,
			updated_at
	`, id).Scan(
		&tl.ID,
		&tl.SquadIDs,
		&tl.Username,
		&tl.Outbound,
		&tl.Strategy,
		&tl.Mode,
		&tl.RawUsed,
		&tl.Quota,
		&tl.RawQuota,
		&tl.Usage,
		&tl.CreatedAt,
		&tl.UpdatedAt,
	)
	return tl, err
}

func (r *PostgreSQLRepository) CreateRateLimiter(limiter constant.RateLimiterCreate) (constant.RateLimiter, error) {
	var rl constant.RateLimiter
	tx, err := r.db.Begin(r.ctx)
	if err != nil {
		return rl, err
	}
	defer tx.Rollback(r.ctx)
	now := time.Now()
	err = tx.QueryRow(
		r.ctx, `
		INSERT INTO rate_limiters
		(
			username,
			outbound,
			strategy,
			connection_type,
			count,
			interval,
			created_at,
			updated_at
		)
		VALUES ($1, $2, $3, $4, $5, $6, $7, $8)
		RETURNING
			id,
			username,
			outbound,
			strategy,
			connection_type,
			count,
			interval,
			created_at,
			updated_at
	`,
		limiter.Username,
		limiter.Outbound,
		limiter.Strategy,
		limiter.ConnectionType,
		limiter.Count,
		limiter.Interval,
		now,
		now,
	).Scan(
		&rl.ID,
		&rl.Username,
		&rl.Outbound,
		&rl.Strategy,
		&rl.ConnectionType,
		&rl.Count,
		&rl.Interval,
		&rl.CreatedAt,
		&rl.UpdatedAt,
	)
	if err != nil {
		return rl, err
	}
	rows := make([][]any, len(limiter.SquadIDs))
	for i, squadID := range limiter.SquadIDs {
		rows[i] = []any{rl.ID, squadID}
	}
	_, err = tx.CopyFrom(
		r.ctx,
		pgx.Identifier{"rate_limiter_to_squad"},
		[]string{"rate_limiter_id", "squad_id"},
		pgx.CopyFromRows(rows),
	)
	if err != nil {
		return rl, err
	}
	rl.SquadIDs = limiter.SquadIDs
	err = tx.Commit(r.ctx)
	if err != nil {
		return rl, err
	}
	return rl, err
}

func (r *PostgreSQLRepository) GetRateLimiters(filters map[string][]string) ([]constant.RateLimiter, error) {
	sb := sqlbuilder.PostgreSQL.NewSelectBuilder().
		Select(
			"id",
			`ARRAY(
				SELECT squad_id
				FROM rate_limiter_to_squad
				WHERE rate_limiter_to_squad.rate_limiter_id = rate_limiters.id
			) as squad_ids`,
			"username",
			"outbound",
			"strategy",
			"connection_type",
			"count",
			"interval",
			"created_at",
			"updated_at",
		).
		From("rate_limiters")

	for k, v := range filters {
		if f, ok := rateLimiterFilters[k]; ok {
			if err := f(sb, v); err != nil {
				return nil, err
			}
		}
	}
	sql, args := sb.Build()
	rows, err := r.db.Query(r.ctx, sql, args...)
	if err != nil {
		return nil, err
	}
	defer rows.Close()
	var result []constant.RateLimiter
	for rows.Next() {
		var rl constant.RateLimiter
		if err := rows.Scan(
			&rl.ID,
			&rl.SquadIDs,
			&rl.Username,
			&rl.Outbound,
			&rl.Strategy,
			&rl.ConnectionType,
			&rl.Count,
			&rl.Interval,
			&rl.CreatedAt,
			&rl.UpdatedAt,
		); err != nil {
			return nil, err
		}
		result = append(result, rl)
	}
	return result, rows.Err()
}

func (r *PostgreSQLRepository) GetRateLimitersCount(filters map[string][]string) (int, error) {
	sb := sqlbuilder.PostgreSQL.NewSelectBuilder().
		Select("COUNT(*)").
		From("rate_limiters")
	for k, v := range filters {
		if f, ok := rateLimiterFilters[k]; ok {
			if err := f(sb, v); err != nil {
				return 0, err
			}
		}
	}
	sql, args := sb.Build()
	var count int
	err := r.db.QueryRow(r.ctx, sql, args...).Scan(&count)
	return count, err
}

func (r *PostgreSQLRepository) GetRateLimiter(id int) (constant.RateLimiter, error) {
	var rl constant.RateLimiter
	err := r.db.QueryRow(r.ctx, `
		SELECT
			id,
			ARRAY(
				SELECT squad_id
				FROM rate_limiter_to_squad
				WHERE rate_limiter_to_squad.rate_limiter_id = rate_limiters.id
			) as squad_ids,
			username,
			outbound,
			strategy,
			connection_type,
			count,
			interval,
			created_at,
			updated_at
		FROM rate_limiters
		WHERE id=$1
	`, id).Scan(
		&rl.ID,
		&rl.SquadIDs,
		&rl.Username,
		&rl.Outbound,
		&rl.Strategy,
		&rl.ConnectionType,
		&rl.Count,
		&rl.Interval,
		&rl.CreatedAt,
		&rl.UpdatedAt,
	)
	return rl, err
}

func (r *PostgreSQLRepository) UpdateRateLimiter(id int, limiter constant.RateLimiterUpdate) (constant.RateLimiter, error) {
	var rl constant.RateLimiter
	err := r.db.QueryRow(
		r.ctx, `
		UPDATE rate_limiters
		SET
			username=$1,
			outbound=$2,
			strategy=$3,
			connection_type=$4,
			count=$5,
			interval=$6,
			updated_at=$7
		WHERE id=$8
		RETURNING
			id,
			ARRAY(
				SELECT squad_id
				FROM rate_limiter_to_squad
				WHERE rate_limiter_to_squad.rate_limiter_id = rate_limiters.id
			) as squad_ids,
			username,
			outbound,
			strategy,
			connection_type,
			count,
			interval,
			created_at,
			updated_at
	`,
		limiter.Username,
		limiter.Outbound,
		limiter.Strategy,
		limiter.ConnectionType,
		limiter.Count,
		limiter.Interval,
		time.Now(),
		id,
	).Scan(
		&rl.ID,
		&rl.SquadIDs,
		&rl.Username,
		&rl.Outbound,
		&rl.Strategy,
		&rl.ConnectionType,
		&rl.Count,
		&rl.Interval,
		&rl.CreatedAt,
		&rl.UpdatedAt,
	)
	return rl, err
}

func (r *PostgreSQLRepository) DeleteRateLimiter(id int) (constant.RateLimiter, error) {
	var rl constant.RateLimiter
	err := r.db.QueryRow(r.ctx, `
		DELETE FROM rate_limiters
		WHERE id=$1
		RETURNING
			id,
			ARRAY(
				SELECT squad_id
				FROM rate_limiter_to_squad
				WHERE rate_limiter_to_squad.rate_limiter_id = rate_limiters.id
			) as squad_ids,
			username,
			outbound,
			strategy,
			connection_type,
			count,
			interval,
			created_at,
			updated_at
	`, id).Scan(
		&rl.ID,
		&rl.SquadIDs,
		&rl.Username,
		&rl.Outbound,
		&rl.Strategy,
		&rl.ConnectionType,
		&rl.Count,
		&rl.Interval,
		&rl.CreatedAt,
		&rl.UpdatedAt,
	)
	return rl, err
}

func init() {
	squadFilters = map[string]Filter{
		"id":               EqualFilter("id"),
		"id_in":            InFilter("id"),
		"pk":               EqualFilter("id"),
		"name":             EqualFilter("name"),
		"created_at_start": GreaterThanFilter("created_at"),
		"created_at_end":   LessThanFilter("created_at"),
		"updated_at_start": GreaterThanFilter("updated_at"),
		"updated_at_end":   LessThanFilter("updated_at"),
		"sort_asc":         SortAscFilter(),
		"sort_desc":        SortDescFilter(),
		"offset":           OffsetFilter(),
		"limit":            LimitFilter(),
	}
	nodeFilters = map[string]Filter{
		"uuid": EqualFilter("uuid"),
		"pk":   EqualFilter("uuid"),
		"name": EqualFilter("name"),
		"squad_id_in": ExistsAndWhereInFilter(
			sqlbuilder.PostgreSQL.NewSelectBuilder().
				Select(
					"squad_id",
				).
				Where(
					"node_to_squad.node_uuid = nodes.uuid",
				).
				From(
					"node_to_squad",
				),
			"node_to_squad.squad_id",
		),
		"created_at_start": GreaterThanFilter("created_at"),
		"created_at_end":   LessThanFilter("created_at"),
		"updated_at_start": GreaterThanFilter("updated_at"),
		"updated_at_end":   LessThanFilter("updated_at"),
		"sort_asc":         SortAscFilter(),
		"sort_desc":        SortDescFilter(),
		"offset":           OffsetFilter(),
		"limit":            LimitFilter(),
	}
	userFilters = map[string]Filter{
		"id": EqualFilter("id"),
		"pk": EqualFilter("id"),
		"squad_id_in": ExistsAndWhereInFilter(
			sqlbuilder.PostgreSQL.NewSelectBuilder().
				Select(
					"squad_id",
				).
				Where(
					"user_to_squad.user_id = users.id",
				).
				From(
					"user_to_squad",
				),
			"user_to_squad.squad_id",
		),
		"username":         EqualFilter("username"),
		"inbound":          EqualFilter("inbound"),
		"type":             EqualFilter("type"),
		"created_at_start": GreaterThanFilter("created_at"),
		"created_at_end":   LessThanFilter("created_at"),
		"updated_at_start": GreaterThanFilter("updated_at"),
		"updated_at_end":   LessThanFilter("updated_at"),
		"sort_asc":         SortAscFilter(),
		"sort_desc":        SortDescFilter(),
		"offset":           OffsetFilter(),
		"limit":            LimitFilter(),
	}
	connectionLimiterFilters = map[string]Filter{
		"id": EqualFilter("id"),
		"pk": EqualFilter("id"),
		"squad_id_in": ExistsAndWhereInFilter(
			sqlbuilder.PostgreSQL.NewSelectBuilder().
				Select(
					"squad_id",
				).
				Where(
					"connection_limiter_to_squad.connection_limiter_id = connection_limiters.id",
				).
				From(
					"connection_limiter_to_squad",
				),
			"connection_limiter_to_squad.squad_id",
		),
		"strategy":         EqualFilter("strategy"),
		"username":         EqualFilter("username"),
		"outbound":         EqualFilter("outbound"),
		"connection_type":  EqualFilter("connection_type"),
		"lock_type":        EqualFilter("lock_type"),
		"created_at_start": GreaterThanFilter("created_at"),
		"created_at_end":   LessThanFilter("created_at"),
		"updated_at_start": GreaterThanFilter("updated_at"),
		"updated_at_end":   LessThanFilter("updated_at"),
		"sort_asc":         SortAscFilter(),
		"sort_desc":        SortDescFilter(),
		"offset":           OffsetFilter(),
		"limit":            LimitFilter(),
	}
	bandwidthLimiterFilters = map[string]Filter{
		"id": EqualFilter("id"),
		"pk": EqualFilter("id"),
		"squad_id_in": ExistsAndWhereInFilter(
			sqlbuilder.PostgreSQL.NewSelectBuilder().
				Select(
					"squad_id",
				).
				Where(
					"bandwidth_limiter_to_squad.bandwidth_limiter_id = bandwidth_limiters.id",
				).
				From(
					"bandwidth_limiter_to_squad",
				),
			"bandwidth_limiter_to_squad.squad_id",
		),
		"strategy":         EqualFilter("strategy"),
		"mode":             EqualFilter("mode"),
		"type":             EqualFilter("type"),
		"username":         EqualFilter("username"),
		"down_start":       SpeedGreaterEqualThanFilter("raw_down"),
		"down_end":         SpeedLessEqualThanFilter("raw_down"),
		"up_start":         SpeedGreaterEqualThanFilter("raw_up"),
		"up_end":           SpeedLessEqualThanFilter("raw_up"),
		"created_at_start": GreaterThanFilter("created_at"),
		"created_at_end":   LessThanFilter("created_at"),
		"updated_at_start": GreaterThanFilter("updated_at"),
		"updated_at_end":   LessThanFilter("updated_at"),
		"sort_asc":         ReplacedSortAscFilter(map[string]string{"down": "raw_down", "up": "raw_up"}),
		"sort_desc":        ReplacedSortDescFilter(map[string]string{"down": "raw_down", "up": "raw_up"}),
		"offset":           OffsetFilter(),
		"limit":            LimitFilter(),
	}
	trafficLimiterFilters = map[string]Filter{
		"id": EqualFilter("id"),
		"pk": EqualFilter("id"),
		"squad_id_in": ExistsAndWhereInFilter(
			sqlbuilder.PostgreSQL.NewSelectBuilder().
				Select(
					"squad_id",
				).
				Where(
					"traffic_limiter_to_squad.traffic_limiter_id = traffic_limiters.id",
				).
				From(
					"traffic_limiter_to_squad",
				),
			"traffic_limiter_to_squad.squad_id",
		),
		"username":         EqualFilter("username"),
		"outbound":         EqualFilter("outbound"),
		"strategy":         EqualFilter("strategy"),
		"mode":             EqualFilter("mode"),
		"used_start":    SpeedGreaterEqualThanFilter("raw_used"),
		"used_end":      SpeedLessEqualThanFilter("raw_used"),
		"quota_start":      SpeedGreaterEqualThanFilter("raw_quota"),
		"quota_end":        SpeedLessEqualThanFilter("raw_quota"),
		"created_at_start": GreaterThanFilter("created_at"),
		"created_at_end":   LessThanFilter("created_at"),
		"updated_at_start": GreaterThanFilter("updated_at"),
		"updated_at_end":   LessThanFilter("updated_at"),
		"sort_asc":         ReplacedSortAscFilter(map[string]string{"used": "raw_used", "quota": "raw_quota"}),
		"sort_desc":        ReplacedSortDescFilter(map[string]string{"used": "raw_used", "quota": "raw_quota"}),
		"offset":           OffsetFilter(),
		"limit":            LimitFilter(),
	}
	rateLimiterFilters = map[string]Filter{
		"id": EqualFilter("id"),
		"pk": EqualFilter("id"),
		"squad_id_in": ExistsAndWhereInFilter(
			sqlbuilder.PostgreSQL.NewSelectBuilder().
				Select(
					"squad_id",
				).
				Where(
					"rate_limiter_to_squad.rate_limiter_id = rate_limiters.id",
				).
				From(
					"rate_limiter_to_squad",
				),
			"rate_limiter_to_squad.squad_id",
		),
		"strategy":         EqualFilter("strategy"),
		"username":         EqualFilter("username"),
		"outbound":         EqualFilter("outbound"),
		"connection_type":  EqualFilter("connection_type"),
		"interval":         EqualFilter("interval"),
		"count_start":      GreaterEqualThanFilter("count"),
		"count_end":        LessEqualThanFilter("count"),
		"created_at_start": GreaterThanFilter("created_at"),
		"created_at_end":   LessThanFilter("created_at"),
		"updated_at_start": GreaterThanFilter("updated_at"),
		"updated_at_end":   LessThanFilter("updated_at"),
		"sort_asc":         SortAscFilter(),
		"sort_desc":        SortDescFilter(),
		"offset":           OffsetFilter(),
		"limit":            LimitFilter(),
	}
}
