package clickhouse

import (
	"context"
	"encoding/json"
	"reflect"

	"github.com/ClickHouse/clickhouse-go/v2/lib/driver"

	"github.com/pkg/errors"
)

func NewMockClient(conn ConnInterface) ClientInterface {
	c := &Client{}
	c.SetConn(conn)
	return c
}

type MockConn struct {
	ExpectedResult   interface{}
	PrepareBatchMock func(ctx context.Context, query string) (driver.Batch, error)
	AsyncInsertMock  func(ctx context.Context, query string, wait bool) error
	QueryRowtMock    func(ctx context.Context, query string, args ...interface{}) driver.Row
	QueryMock        func(ctx context.Context, query string, args ...interface{}) (driver.Rows, error)
	Client
}

func (c *MockConn) Select(ctx context.Context, dest interface{}, query string, args ...interface{}) error {
	payload, err := json.Marshal(c.ExpectedResult)
	if err != nil {
		return errors.WithStack(err)
	}

	err = json.Unmarshal(payload, dest)
	if err != nil {
		return errors.WithStack(err)
	}

	return nil
}

func (c *MockConn) PrepareBatch(ctx context.Context, query string) (driver.Batch, error) {
	return c.PrepareBatchMock(ctx, query)
}

func (c *MockConn) AsyncInsert(ctx context.Context, query string, wait bool) error {
	return c.AsyncInsertMock(ctx, query, wait)
}

func (c *MockConn) QueryRow(ctx context.Context, query string, args ...interface{}) driver.Row {
	return c.QueryRowtMock(ctx, query, args...)
}

func (c *MockConn) Query(ctx context.Context, query string, args ...interface{}) (driver.Rows, error) {
	return RowsMock{}, nil
}

func (c *MockConn) Exec(ctx context.Context, query string, args ...interface{}) error {
	return nil
}

type ColumnTypeMock struct{}

func (c ColumnTypeMock) Name() string {
	return "name"
}

func (c ColumnTypeMock) Nullable() bool {
	return true
}

func (c ColumnTypeMock) ScanType() reflect.Type {
	return reflect.TypeOf("")
}

func (c ColumnTypeMock) DatabaseTypeName() string {
	return ""
}

type RowsMock struct {
	RowsResult interface{}
}

func (r RowsMock) Next() bool {
	return false
}

func (r RowsMock) Scan(dest ...interface{}) error {
	return nil
}

func (r RowsMock) ScanStruct(dest interface{}) error {
	return nil
}

func (r RowsMock) ColumnTypes() []driver.ColumnType {
	return []driver.ColumnType{ColumnTypeMock{}}
}

func (r RowsMock) Totals(dest ...interface{}) error {
	return nil
}

func (r RowsMock) Columns() []string {
	return nil
}

func (r RowsMock) Close() error {
	return nil
}

func (r RowsMock) Err() error {
	return nil
}

type RowMock struct {
	RowResult interface{}
}

func (r RowMock) Err() error {
	return nil
}

func (r RowMock) Scan(dest ...interface{}) error {
	if len(dest) != 0 {
		dest[0] = r.RowResult
	}

	return nil
}

func (r RowMock) ScanStruct(dest interface{}) error {
	return nil
}
