package pgdb

import (
	"context"
	"crypto/tls"
	"crypto/x509"
	"database/sql"
	"fmt"
	"os"

	"github.com/jackc/pgx/v5"
	"github.com/jackc/pgx/v5/stdlib"
	"github.com/jmoiron/sqlx"
)

// Интерфейс передается в неэкспортируемые функции
// нужен для того, чтобы не зависило от того, что передаём
// транзацию или обычное соединение
type Driver interface {
	// QueryContext executes a query that returns rows, typically a SELECT.
	// The args are for any placeholder parameters in the query.
	QueryContext(ctx context.Context, query string, args ...any) (*sql.Rows, error)

	// QueryRowContext executes a query that is expected to return at most one row.
	// QueryRowContext always returns a non-nil value. Errors are deferred until
	// [Row]'s Scan method is called.
	// If the query selects no rows, the [*Row.Scan] will return [ErrNoRows].
	// Otherwise, [*Row.Scan] scans the first selected row and discards
	// the rest.
	QueryRowContext(ctx context.Context, query string, args ...any) *sql.Row

	// ExecContext executes a query without returning any rows.
	// The args are for any placeholder parameters in the query.
	ExecContext(ctx context.Context, query string, args ...any) (sql.Result, error)
}

type PostgresConfig struct {
	Host        string
	Port        uint16
	Username    string
	Password    string
	Database    string
	Schema      string
	SSLMode     string
	SSLRootCert string
}

type client struct {
	config PostgresConfig
	db     *sqlx.DB
}

func NewClient(cfg PostgresConfig) (*client, error) {
	rootCertPool := x509.NewCertPool()

	caCert, err := os.ReadFile(cfg.SSLRootCert)
	if err != nil {
		return nil, fmt.Errorf("failed to read CA cert: %w", err)
	}

	if ok := rootCertPool.AppendCertsFromPEM(caCert); !ok {
		return nil, fmt.Errorf("failed to append CA cert to pool")
	}

	tlsConfig := &tls.Config{
		RootCAs:    rootCertPool,
		MinVersion: tls.VersionTLS12,
		//nolint:gosec // TODO: set server name
		InsecureSkipVerify: true,
	}

	config, err := pgx.ParseConfig("")
	if err != nil {
		return nil, fmt.Errorf("failed to parse [empty] config: %w", err)
	}

	config.Host = cfg.Host
	config.Port = cfg.Port
	config.Database = cfg.Database
	config.User = cfg.Username
	config.Password = cfg.Password
	config.TLSConfig = tlsConfig
	config.RuntimeParams = map[string]string{"sslmode": cfg.SSLMode}

	db := sqlx.NewDb(stdlib.OpenDB(*config), "pgx")

	if err := db.Ping(); err != nil {
		return nil, fmt.Errorf("failed to ping postgres: %w", err)
	}

	return &client{
		config: cfg,
		db:     db,
	}, nil
}

func countOffset(page, pageSize uint64) uint64 {
	if page == 0 {
		page = 1
	}

	if pageSize == 0 {
		pageSize = DefaultPaginationPageSize
	}

	return (page - 1) * pageSize
}
