package gormdb

import (
	"fmt"
	"github.com/UpmeshLTD/urus-aio/infrastructure/logging"
	"github.com/pkg/errors"
	gormlogger "gorm.io/gorm/logger"
	"io"
	stdlog "log"
	"os"
	"time"

	"github.com/UpmeshLTD/urus-aio/shipment/config"
	"github.com/UpmeshLTD/urus-aio/shipment/log"
	"github.com/avast/retry-go"
	"gorm.io/driver/postgres"
	"gorm.io/gorm"
)

type GormDB interface {
	// DB returns the internal gorm.DB instance.
	DB() *gorm.DB
}

type gormImpl struct {
	db *gorm.DB
}

func NewGormDB(logger log.Logger, cfg config.Config) (GormDB, error) {
	dbConfig := cfg.GetDatabaseConfig()
	dsn := fmt.Sprintf("host=%s user=%s password=%s dbname=%s port=%d sslmode=disable",
		dbConfig.Host, dbConfig.Username, dbConfig.Password, dbConfig.Database, dbConfig.Port)

	var (
		db  *gorm.DB
		err error
	)

	err = retry.Do(func() error {
		gormConfig := &gorm.Config{}

		if cfg.GetAppConfig().EnableLogGorm {
			// Send gorm logs to writer
			logWriter := logging.Writer()
			if logWriter != nil && *logWriter != nil {
				// write logs to both std log and custom log writer
				writer := io.MultiWriter(os.Stdout, *logWriter)
				gormConfig.Logger = gormlogger.New(stdlog.New(writer, "\r\n", stdlog.LstdFlags), gormlogger.Config{
					SlowThreshold:             200 * time.Millisecond,
					LogLevel:                  gormlogger.Info,
					IgnoreRecordNotFoundError: false,
					Colorful:                  cfg.GetAppConfig().EnableLogColorized,
				})
			} else {
				gormConfig.Logger = gormlogger.Default
			}
		} else {
			gormConfig.Logger = gormlogger.Default.LogMode(gormlogger.Silent)
		}

		db, err = gorm.Open(postgres.Open(dsn), gormConfig)
		return err
	},
		retry.Attempts(dbConfig.RetryAttempts),
		retry.Delay(time.Second),
	)

	if err != nil {
		stdlog.Println(errors.Wrap(err, "failed to initialize GormDB"))
		return nil, err
	}

	logger.Info("successfully initialize GormDB")
	if cfg.IsDevelopment() && cfg.GetAppConfig().EnableLogGorm {
		db = db.Debug()
	}

	return &gormImpl{
		db: db,
	}, nil
}

func Wrap(db *gorm.DB) GormDB {
	return &gormImpl{
		db: db,
	}
}

// DB returns the internal gorm.DB instance.
func (impl *gormImpl) DB() *gorm.DB {
	return impl.db
}
