// configures database
package dbconf

import (
	"database/sql"
	"fmt"
	"os"
	"path/filepath"
	"sort"
	"strings"

	"code.justin.tv/d8a/iceman/lib/queries"
	"code.justin.tv/d8a/iceman/lib/tables"
	"errors"
	_ "github.com/go-sql-driver/mysql"
	"github.com/kr/logfmt"
	"github.com/kylelemons/go-gypsy/yaml"
	_ "github.com/lib/pq"
)

// DBConf contains configuration details
// about the corresponding database
type DBConf struct {
	Dir           string
	Env           string
	DBUrl         string
	PgSchema      string
	DriverName    string
	DriverQueries queries.DriverQueries
}

var PostgresDriver = "postgres"
var MySqlDriver = "mysql"
var SqliteDriver = "sqlite"

// NewDBConf extracts configuration details from the given file
// and returns a new DBConf
func NewDBConf(p string, env string, pgschema string, dir string) (*DBConf, error) {

	configFile := filepath.Join(p, "dbconf.yaml")

	f, err := yaml.ReadFile(configFile)
	if err != nil {
		return nil, err
	}

	driverName, err := f.Get(fmt.Sprintf("%s.driver", env))
	if err != nil {
		driverName = PostgresDriver
	}

	db, err := f.Get(fmt.Sprintf("%s.database", env))
	if err != nil {
		return nil, err
	}
	db = os.ExpandEnv(db)

	var driverQueries queries.DriverQueries

	switch driverName {
	case PostgresDriver:
		driverQueries = &queries.PostgresQueries{}
	case MySqlDriver:
		driverQueries = &queries.MySQLQueries{}
	case SqliteDriver:
		driverQueries = &queries.SqliteQueries{}
	default:
		return nil, errors.New(fmt.Sprintf("Database driver \"%v\" is not supported.", driverName))
	}

	return &DBConf{
		Dir:           filepath.Join(p, dir),
		Env:           env,
		DBUrl:         db,
		PgSchema:      pgschema,
		DriverName:    driverName,
		DriverQueries: driverQueries,
	}, nil
}

// OpenDBFromDBConf opens a database and
// configures it based on the given DBConf
func OpenDBFromDBConf(conf *DBConf) (*sql.DB, error) {
	db, err := sql.Open(conf.DriverName, conf.DBUrl)
	if err != nil {
		return nil, err
	}

	// if a postgres schema has been specified, apply it
	if conf.PgSchema != "" {
		if _, err := db.Exec("SET search_path TO " + conf.PgSchema); err != nil {
			return nil, err
		}
	}

	err = tables.VerifyIcemanTables(db, conf.DriverQueries)
	if err != nil {
		return nil, err
	}

	return db, nil
}

type DbOptions map[string]string

func (self *DbOptions) HandleLogfmt(key, val []byte) error {
	(*self)[string(key)] = string(val)
	return nil
}

func (self *DBConf) SubstituteDBParams(overrideParams map[string]string) error {

	if len(overrideParams) == 0 || self.DBUrl == "" {
		return nil
	}

	inputProperties := make(DbOptions)

	err := logfmt.Unmarshal([]byte(self.DBUrl), &inputProperties)
	if err != nil {
		return err
	}

	for key, value := range overrideParams {
		inputProperties[key] = value
	}

	//This is a bit silly, but keys in maps are iterated randomly in go and so getting a consistent
	//connection string for things like unit tests requires actual sorting
	var sortedKeys []string
	for key, _ := range inputProperties {
		sortedKeys = append(sortedKeys, key)
	}
	sort.Strings(sortedKeys)

	output := ""
	for _, key := range sortedKeys {
		output = fmt.Sprintf("%s %s=%s", output, key, inputProperties[key])
	}

	self.DBUrl = strings.TrimSpace(output)
	return nil
}
