package xsql

import (
	"context"
	"database/sql/driver"

	"github.com/ydb-platform/ydb-go-sdk/v3/internal/params"
	"github.com/ydb-platform/ydb-go-sdk/v3/internal/stack"
	"github.com/ydb-platform/ydb-go-sdk/v3/internal/xerrors"
	"github.com/ydb-platform/ydb-go-sdk/v3/internal/xsql/badconn"
	"github.com/ydb-platform/ydb-go-sdk/v3/internal/xsql/common"
	"github.com/ydb-platform/ydb-go-sdk/v3/internal/xsql/gtrace"
)

type Stmt struct {
	conn      *Conn
	processor interface {
		Exec(ctx context.Context, sql string, params *params.Params) (driver.Result, error)
		Query(ctx context.Context, sql string, params *params.Params) (common.Rows, error)
	}
	sql string
	ctx context.Context //nolint:containedctx
}

var (
	_ driver.Stmt             = &Stmt{}
	_ driver.StmtQueryContext = &Stmt{}
	_ driver.StmtExecContext  = &Stmt{}
)

func (stmt *Stmt) QueryContext(ctx context.Context, args []driver.NamedValue) (_ driver.Rows, finalErr error) {
	onDone := gtrace.DatabaseSQLOnStmtQuery(stmt.conn.connector.Trace(), &ctx,
		stack.FunctionID("database/sql.(*Stmt).QueryContext" /*stack.Package("database/sql")*/),
		stmt.ctx, stmt.sql,
	)
	defer func() {
		onDone(finalErr)
	}()

	if !stmt.conn.cc.IsValid() {
		return nil, xerrors.WithStackTrace(xerrors.Retryable(errNotReadyConn,
			xerrors.Invalid(stmt),
			xerrors.Invalid(stmt.conn),
			xerrors.Invalid(stmt.conn.cc),
		))
	}

	sql, params, err := stmt.conn.toYdb(stmt.sql, args...)
	if err != nil {
		return nil, xerrors.WithStackTrace(err)
	}

	rows, err := stmt.processor.Query(ctx, sql, params)
	if err != nil {
		return nil, xerrors.WithStackTrace(badconn.Map(err))
	}

	return newRows(ctx, rows), nil
}

func (stmt *Stmt) ExecContext(ctx context.Context, args []driver.NamedValue) (_ driver.Result, finalErr error) {
	onDone := gtrace.DatabaseSQLOnStmtExec(stmt.conn.connector.Trace(), &ctx,
		stack.FunctionID("database/sql.(*Stmt).ExecContext" /*stack.Package("database/sql")*/),
		stmt.ctx, stmt.sql,
	)
	defer func() {
		onDone(finalErr)
	}()

	if !stmt.conn.cc.IsValid() {
		return nil, xerrors.WithStackTrace(xerrors.Retryable(errNotReadyConn,
			xerrors.Invalid(stmt),
			xerrors.Invalid(stmt.conn),
			xerrors.Invalid(stmt.conn.cc),
		))
	}

	sql, params, err := stmt.conn.toYdb(stmt.sql, args...)
	if err != nil {
		return nil, xerrors.WithStackTrace(err)
	}

	result, err := stmt.processor.Exec(ctx, sql, params)
	if err != nil {
		return nil, xerrors.WithStackTrace(badconn.Map(err))
	}

	return result, nil
}

func (stmt *Stmt) NumInput() int {
	return -1
}

func (stmt *Stmt) Close() (finalErr error) {
	var (
		ctx    = stmt.ctx
		onDone = gtrace.DatabaseSQLOnStmtClose(stmt.conn.connector.Trace(), &ctx,
			stack.FunctionID("database/sql.(*Stmt).Close" /*stack.Package("database/sql")*/),
		)
	)
	defer func() {
		onDone(finalErr)
	}()

	return nil
}

func (stmt *Stmt) Exec([]driver.Value) (driver.Result, error) {
	return nil, errDeprecated
}

func (stmt *Stmt) Query([]driver.Value) (driver.Rows, error) {
	return nil, errDeprecated
}
