package database

import (
	"cargo-erp-backend/internal/config"
	"cargo-erp-backend/internal/logger"
	"fmt"
	"net/url"
	"sync"
	"time"

	"go.uber.org/zap"
	"gorm.io/driver/postgres"
	"gorm.io/gorm"
)

type DatabaseInterface interface {
	Connect()
	Close()
	Get(key string) *gorm.DB
	RegisterCallback()
}
type Database struct {
	Config    config.ConfigInterface
	DB        map[string]*gorm.DB
	dbLock    sync.RWMutex
	Logger    *zap.Logger
	AppLogger *zap.Logger
}

func NewDatabase(config config.ConfigInterface, logger *zap.Logger, app_logger *zap.Logger) DatabaseInterface {
	return &Database{
		Config:    config,
		Logger:    logger,
		AppLogger: app_logger,
	}
}
func (d *Database) Connect() {
	d.dbLock.Lock()
	defer d.dbLock.Unlock()
	db := make(map[string]*gorm.DB)
	username := d.Config.Get("database.username").(string)
	password := d.Config.Get("database.password").(string)
	dbname := d.Config.Get("database.database").(string)
	host_master := fmt.Sprintf("%v", d.Config.Get("database.master.host"))
	port_master := fmt.Sprintf("%v", d.Config.Get("database.master.port"))
	ssl_master := d.Config.Get("database.master.ssl")
	dsnMaster := fmt.Sprintf("postgres://%s:%s@%s:%s/%s?sslmode=%s",
		url.PathEscape(username),
		url.PathEscape(password),
		host_master,
		port_master,
		dbname,
		ssl_master,
	)
	d.AppLogger.Info("Connect To Master DB")
	gormLogger := logger.NewGormZapLogger(d.Logger, 5*time.Second)
	dbMaster, err := gorm.Open(postgres.New(postgres.Config{
		DSN:                  dsnMaster,
		PreferSimpleProtocol: true,
	}), &gorm.Config{
		Logger: gormLogger,
	})
	if err != nil {
		d.AppLogger.Fatal(err.Error())
		return
	}
	db["master"] = dbMaster
	d.AppLogger.Info("DB Master Connected")

	host_slave := fmt.Sprintf("%v", d.Config.Get("database.slave.host"))
	port_slave := fmt.Sprintf("%v", d.Config.Get("database.slave.port"))
	ssl_slave := d.Config.Get("database.slave.ssl")
	dsnSlave := fmt.Sprintf("postgres://%s:%s@%s:%s/%s?sslmode=%s",
		url.PathEscape(username),
		url.PathEscape(password),
		host_slave,
		port_slave,
		dbname,
		ssl_slave,
	)
	d.AppLogger.Info("Connect To Slave DB")
	dbSlave, err := gorm.Open(postgres.New(postgres.Config{
		DSN:                  dsnSlave,
		PreferSimpleProtocol: true,
	}), &gorm.Config{
		Logger: gormLogger,
	})
	if err != nil {
		d.AppLogger.Fatal(err.Error())
		return
	}
	db["slave"] = dbSlave
	d.DB = db
	d.AppLogger.Info("DB Connection Done")
}
func (d *Database) RegisterCallback() {
	d.Config.RegisterOnChangeCallback(d.ReloadDB)
}
func (d *Database) Get(key string) *gorm.DB {
	d.dbLock.RLock()
	defer d.dbLock.RUnlock()
	if v, ok := d.DB[key]; ok {
		return v
	}
	return nil
}
func (d *Database) Close() {
	d.dbLock.Lock()
	defer d.dbLock.Unlock()
	d.AppLogger.Info("Close Database Connection Start")
	for k, db := range d.DB {
		if db != nil {
			sqlDb, _ := db.DB()
			sqlDb.Close()
			d.AppLogger.Info(fmt.Sprintf("Close Database Connection %s", k))
		}
	}
	d.AppLogger.Info("Close Database Connection Done")
}
func (d *Database) ReloadDB() {
	d.AppLogger.Info("Reload Connecting Database")
	d.Close()
	d.Connect()
	d.AppLogger.Info("Reload Connecting Database Done")
}
