package db

import (
	"fmt"
	"sync"

	"github.com/fiskerinc/cloud-services/pkg/logger"
	"github.com/fiskerinc/cloud-services/pkg/utils/envtool"
	"github.com/go-pg/pg/v10"
	"github.com/go-pg/pg/v10/orm"
	"github.com/pkg/errors"

	pgtrace "gopkg.in/DataDog/dd-trace-go.v1/contrib/go-pg/pg.v10"
)

var (
	ErrTableDoesntExist       = errors.New("table doesn't exist")
	ErrTableAndModelDontMatch = errors.New("table and model columns don't match")
)

func GetDefaultConn() *pg.DB {
	host := envtool.GetEnv("DB_HOST", "localhost")
	port := envtool.GetEnv("DB_PORT", "5432")
	user := envtool.GetEnv("DB_USER", "postgres")
	password := envtool.GetEnv("DB_PASSWORD", "REPLACE_ME")
	dbname := envtool.GetEnv("DB_NAME", "postgres")
	sslmode := envtool.GetEnv("DB_SSLMODE", "disable")
	poolSize := envtool.GetEnvInt("DB_POOLSIZE", 10)
	addr := fmt.Sprintf("%v:%v", host, port)

	logger.Info().Msgf("Initializing database connection %s", addr)
	conn_str := fmt.Sprintf("postgres://%s:%s@%s:%s/%s?sslmode=%s", user, password, host, port, dbname, sslmode)
	opts, err := pg.ParseURL(conn_str)
	opts.PoolSize = poolSize
	if err != nil {
		logger.Error().Err(err).Send()
	}
	conn := pg.Connect(opts)

	// Wrap the connection with the APM hook.
	pgtrace.Wrap(conn)

	return conn
}

type DBClientInterface interface {
	GetConn() *pg.DB
	SetConn(*pg.DB) error
	InitSchema([]interface{}) error
	RegisterManyToManyRel(tables []interface{})
	Close() error
}

type DBClient struct {
	driver *pg.DB
	once   sync.Once
}

func (db *DBClient) Close() error {
	logger.Info().Msg("Closing database connection")

	if db.driver == nil {
		return nil
	}

	return db.driver.Close()
}

func (db *DBClient) GetConn() *pg.DB {
	db.once.Do(func() {
		if db.driver != nil {
			return
		}
		db.driver = GetDefaultConn()
	})

	return db.driver
}

func (db *DBClient) SetConn(d *pg.DB) error {
	var err error
	if db.driver != nil {
		err = db.driver.Close()
	}

	db.driver = d
	return err
}

func (db *DBClient) InitSchema(models []interface{}) error {
	migrator := Migrator{
		DB: db.GetConn(),
	}
	defer migrator.Close()

	for _, model := range models {
		err := migrator.Check(model)
		if err != nil {
			return err
		}
	}

	return nil
}

func (db *DBClient) RegisterManyToManyRel(tables []interface{}) {
	for _, table := range tables {
		orm.RegisterTable(table)
	}
}
