package intersystems

import (
	"context"
	"database/sql"
	"database/sql/driver"
	"errors"
	"fmt"
	"net"
	"unicode"

	_ "io"
	_ "math"
	_ "reflect"
	_ "strconv"
	_ "strings"
	_ "time"
	_ "unsafe"

	"github.com/caretdev/go-irisnative/src/connection"
)

var (
	ErrCouldNotDetectUsername = errors.New("intersystems: Could not detect default username. Please provide one explicitly")
)

var (
	_ driver.Driver = Driver{}
)

type values map[string]string

// Driver implements database/sql/driver.Driver.
type Driver struct{}

func (d Driver) Open(name string) (driver.Conn, error) {
	return Open(name)
}

func init() {
	sql.Register("intersystems", &Driver{})
	sql.Register("iris", &Driver{})
}

func Open(dsn string) (_ driver.Conn, err error) {
	c, err := NewConnector(dsn)
	if err != nil {
		return nil, err
	}
	return c.open(context.Background())
}

type conn struct {
	c  connection.Connection
	tx bool
}

func (c *Connector) open(ctx context.Context) (cn *conn, err error) {
	o := make(values)
	for k, v := range c.opts {
		o[k] = v
	}
	host := o["host"]
	addr := net.JoinHostPort(host, o["port"])
	namespace := o["namespace"]
	login := o["user"]
	password := o["password"]

	cn = &conn{}

	cn.c, err = connection.Connect(addr, namespace, login, password)
	if err != nil {
		return nil, err
	}
	return cn, nil
}

func (cn *conn) Begin() (driver.Tx, error) {
	return cn.c.BeginTx(driver.TxOptions{})
}

func (cn *conn) BeginTx(ctx context.Context, opts driver.TxOptions) (driver.Tx, error) {
	return cn.c.BeginTx(opts)
}

func (cn *conn) Close() (err error) {
	cn.c.Disconnect()
	return nil
}

func (cn *conn) Prepare(q string) (st driver.Stmt, err error) {
	return cn.c.Prepare(q)
}

func (cn *conn) Commit() error {
	if !cn.tx {
		panic("transaction already closed")
	}
	cn.tx = false
	cn.c.Commit()
	return nil
}

func (cn *conn) Rollback() error {
	if !cn.tx {
		panic("transaction already closed")
	}
	cn.tx = false
	cn.c.Rollback()
	return nil
}

func (cn *conn) Exec(query string, args []driver.NamedValue) (res driver.Result, err error) {
	parameters := make([]interface{}, len(args))
	for i, a := range args {
		parameters[i] = a
	}
	_, err = cn.c.DirectUpdate(query, parameters...)
	if err != nil {
		return nil, err
	}
	return res, nil
}

func (cn *conn) Query(query string, args []driver.NamedValue) (rows driver.Rows, err error) {
	parameters := make([]interface{}, len(args))
	for i, a := range args {
		parameters[i] = a
	}
	// var rs *connection.ResultSet
	_, err = cn.c.Query(query, parameters...)
	if err != nil {
		return nil, err
	}
	// rows = &connection.Rows{
	// 	cn: cn.c,
	// 	rs: rs,
	// }
	return
}

func parseOpts(name string, o values) error {
	s := newScanner(name)

	for {
		var (
			keyRunes, valRunes []rune
			r                  rune
			ok                 bool
		)

		if r, ok = s.SkipSpaces(); !ok {
			break
		}

		// Scan the key
		for !unicode.IsSpace(r) && r != '=' {
			keyRunes = append(keyRunes, r)
			if r, ok = s.Next(); !ok {
				break
			}
		}

		// Skip any whitespace if we're not at the = yet
		if r != '=' {
			r, ok = s.SkipSpaces()
		}

		// The current character should be =
		if r != '=' || !ok {
			return fmt.Errorf(`missing "=" after %q in connection info string"`, string(keyRunes))
		}

		// Skip any whitespace after the =
		if r, ok = s.SkipSpaces(); !ok {
			// If we reach the end here, the last value is just an empty string as per libpq.
			o[string(keyRunes)] = ""
			break
		}

		if r != '\'' {
			for !unicode.IsSpace(r) {
				if r == '\\' {
					if r, ok = s.Next(); !ok {
						return fmt.Errorf(`missing character after backslash`)
					}
				}
				valRunes = append(valRunes, r)

				if r, ok = s.Next(); !ok {
					break
				}
			}
		} else {
		quote:
			for {
				if r, ok = s.Next(); !ok {
					return fmt.Errorf(`unterminated quoted string literal in connection string`)
				}
				switch r {
				case '\'':
					break quote
				case '\\':
					r, _ = s.Next()
					fallthrough
				default:
					valRunes = append(valRunes, r)
				}
			}
		}

		o[string(keyRunes)] = string(valRunes)
	}

	return nil
}
