package postgresql

import (
	"context"
	"database/sql"
	"errors"
	"fmt"
	"net/url"
	"time"

	"github.com/1Panel-dev/1Panel/agent/buserr"
	"github.com/1Panel-dev/1Panel/agent/utils/postgresql/client"
	_ "github.com/jackc/pgx/v5/stdlib"
)

type PostgresqlClient interface {
	Create(info client.CreateInfo) error
	CreateUser(info client.CreateInfo, withDeleteDB bool) error
	Delete(info client.DeleteInfo) error
	ChangePrivileges(info client.Privileges) error
	ChangePassword(info client.PasswordChangeInfo) error

	Backup(info client.BackupInfo) error
	Recover(info client.RecoverInfo) error
	SyncDB() ([]client.SyncDBInfo, error)
	Close()
}

func NewPostgresqlClient(conn client.DBInfo) (PostgresqlClient, error) {
	if conn.From == "local" {
		connArgs := []string{"exec", "-e", fmt.Sprintf("PGPASSWORD=%s", conn.Password), conn.Address, "psql", "-t", "-U", conn.Username, "-c"}
		return client.NewLocal(connArgs, conn.Address, conn.Username, conn.Password, conn.Database), nil
	}
	escapedUsername := url.QueryEscape(conn.Username)
	escapedPassword := url.QueryEscape(conn.Password)
	if len(conn.InitialDB) == 0 {
		conn.InitialDB = escapedUsername
	}
	connArgs := fmt.Sprintf("postgres://%s:%s@%s:%d/%s?sslmode=disable", escapedUsername, escapedPassword, conn.Address, conn.Port, conn.InitialDB)
	db, err := sql.Open("pgx", connArgs)
	if err != nil {
		return nil, err
	}

	ctx, cancel := context.WithTimeout(context.Background(), time.Duration(conn.Timeout)*time.Second)
	defer cancel()
	if err := db.PingContext(ctx); err != nil {
		return nil, err
	}
	if errors.Is(ctx.Err(), context.DeadlineExceeded) {
		return nil, buserr.New("ErrExecTimeOut")
	}

	return client.NewRemote(client.Remote{
		Client:   db,
		From:     "remote",
		Database: conn.Database,
		User:     conn.Username,
		Password: conn.Password,
		Address:  conn.Address,
		Port:     conn.Port,
	}), nil
}
