package query

import (
	"context"
	"database/sql"
	"database/sql/driver"
	"errors"
	"sync"
	"testing"
)

var errScannerTestRows = errors.New("rows failed after first result")
var registerScannerTestDriver sync.Once

func TestScannedStorageValuePreservesDriverText(t *testing.T) {
	tests := []struct {
		name string
		in   interface{}
		want interface{}
	}{
		{name: "decimal zero scale", in: []byte("0.000000000"), want: "0.000000000"},
		{name: "large decimal coefficient and scale", in: []byte("12345678901234567890123456789.123456789"), want: "12345678901234567890123456789.123456789"},
		{name: "numeric-looking varchar", in: []byte("001.2300"), want: "001.2300"},
		{name: "native driver value", in: int64(7), want: int64(7)},
	}
	for _, test := range tests {
		t.Run(test.name, func(t *testing.T) {
			if got := scannedStorageValue(test.in); got != test.want {
				t.Fatalf("scannedStorageValue(%#v) = %#v, want %#v", test.in, got, test.want)
			}
		})
	}
}

func TestScanRowsToSObjectsReturnsTerminalRowsError(t *testing.T) {
	const driverName = "nexuscrm-query-scanner-terminal-error"
	registerScannerTestDriver.Do(func() { sql.Register(driverName, scannerTerminalErrorDriver{}) })
	db, err := sql.Open(driverName, "")
	if err != nil {
		t.Fatalf("sql.Open: %v", err)
	}
	defer db.Close()
	rows, err := db.QueryContext(context.Background(), "SELECT value")
	if err != nil {
		t.Fatalf("QueryContext: %v", err)
	}
	defer rows.Close()

	got, err := ScanRowsToSObjects(rows)
	if got != nil || !errors.Is(err, errScannerTestRows) {
		t.Fatalf("result = %#v, error = %v, want terminal rows error", got, err)
	}
}

func TestScanRowsToMapsReturnsTerminalRowsError(t *testing.T) {
	const driverName = "nexuscrm-query-scanner-terminal-error"
	registerScannerTestDriver.Do(func() {
		sql.Register(driverName, scannerTerminalErrorDriver{})
	})
	db, err := sql.Open(driverName, "")
	if err != nil {
		t.Fatalf("sql.Open: %v", err)
	}
	defer db.Close()
	rows, err := db.QueryContext(context.Background(), "SELECT value")
	if err != nil {
		t.Fatalf("QueryContext: %v", err)
	}
	defer rows.Close()

	got, err := ScanRowsToMaps(rows)
	if got != nil || !errors.Is(err, errScannerTestRows) {
		t.Fatalf("result = %#v, error = %v, want terminal rows error", got, err)
	}
}

type scannerTerminalErrorDriver struct{}

func (scannerTerminalErrorDriver) Open(string) (driver.Conn, error) {
	return scannerTerminalErrorConn{}, nil
}

type scannerTerminalErrorConn struct{}

func (scannerTerminalErrorConn) Prepare(string) (driver.Stmt, error) {
	return nil, errors.New("not implemented")
}
func (scannerTerminalErrorConn) Close() error              { return nil }
func (scannerTerminalErrorConn) Begin() (driver.Tx, error) { return nil, errors.New("not implemented") }
func (scannerTerminalErrorConn) QueryContext(context.Context, string, []driver.NamedValue) (driver.Rows, error) {
	return &scannerTerminalErrorRows{}, nil
}

type scannerTerminalErrorRows struct {
	returned bool
}

func (*scannerTerminalErrorRows) Columns() []string { return []string{"Value"} }
func (*scannerTerminalErrorRows) Close() error      { return nil }
func (r *scannerTerminalErrorRows) Next(dest []driver.Value) error {
	if r.returned {
		return errScannerTestRows
	}
	r.returned = true
	dest[0] = "first"
	return nil
}

var _ driver.QueryerContext = scannerTerminalErrorConn{}
var _ driver.Rows = (*scannerTerminalErrorRows)(nil)
