package helpers

import (
	"cargo-erp-backend/internal/config"
	"cargo-erp-backend/internal/database"
	"cargo-erp-backend/internal/domain"
	"encoding/json"
	"reflect"

	"github.com/redis/go-redis/v9"
	"go.uber.org/zap"
	"golang.org/x/crypto/bcrypt"
	"gorm.io/gorm"
)

type HelperInterface interface {
	GetDB(key string) *gorm.DB
	GetRedis(key string) *redis.Client
	Config() config.ConfigInterface
	Log() *zap.Logger
	HashPassword(password string) string
	VerifyPassword(hashedPassword, password string) bool
	StringFromPtr(ptr *string) string
	StructTobyte(data interface{}) ([]byte, error)
	CloneStructWithoutField(input interface{}, field string) (map[string]interface{}, error)
	GetAuthInfo(authinfo string) (*domain.User, error)
	MapStructToStruct(source interface{}, source_tag string, target interface{}, target_tag string)
}
type Helper struct {
	Conf    config.ConfigInterface
	DB      database.DatabaseInterface
	Logger  *zap.Logger
	RedisDB database.RedisInterface
}

func NewHelper(config config.ConfigInterface, db database.DatabaseInterface, logger *zap.Logger, dbredis database.RedisInterface) HelperInterface {
	return &Helper{
		Conf:    config,
		DB:      db,
		Logger:  logger,
		RedisDB: dbredis,
	}
}
func (h *Helper) GetDB(key string) *gorm.DB {
	return h.DB.Get(key)
}
func (h *Helper) GetRedis(key string) *redis.Client {
	return h.RedisDB.Get(key)
}
func (h *Helper) Config() config.ConfigInterface {
	return h.Conf
}
func (h *Helper) Log() *zap.Logger {
	return h.Logger
}
func (h *Helper) HashPassword(password string) string {
	bytes, _ := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
	return string(bytes)
}
func (h *Helper) VerifyPassword(hashedPassword, password string) bool {
	err := bcrypt.CompareHashAndPassword([]byte(hashedPassword), []byte(password))
	return err == nil
}
func (h *Helper) StringFromPtr(ptr *string) string {
	if ptr == nil {
		return ""
	}
	return *ptr
}
func (h *Helper) StructTobyte(data interface{}) ([]byte, error) {
	var output []byte
	s, err := json.Marshal(data)
	if err != nil {
		return output, err
	}
	output = s
	return output, nil
}
func (h *Helper) CloneStructWithoutField(input interface{}, field string) (map[string]interface{}, error) {

	data, err := json.Marshal(input)
	if err != nil {
		return nil, err
	}

	var result map[string]interface{}

	if err := json.Unmarshal(data, &result); err != nil {
		return nil, err
	}
	if len(field) > 0 {
		delete(result, field)
	}

	return result, nil
}

func (h *Helper) GetAuthInfo(authinfo string) (*domain.User, error) {

	var auth domain.User
	if err := json.Unmarshal([]byte(authinfo), &auth); err != nil {
		return nil, err
	}
	return &auth, nil
}

func (h *Helper) MapStructToStruct(source interface{}, source_tag string, target interface{}, target_tag string) {
	srcVal := reflect.ValueOf(source)
	if srcVal.Kind() == reflect.Ptr {
		srcVal = srcVal.Elem()
	}
	srcType := srcVal.Type()

	// 1. Kumpulkan data dari struct asal ke dalam map [nama_tag] -> value
	jsonMap := make(map[string]reflect.Value)
	for i := 0; i < srcVal.NumField(); i++ {
		jsonTag := srcType.Field(i).Tag.Get(source_tag)
		if jsonTag != "" && jsonTag != "-" {
			jsonMap[jsonTag] = srcVal.Field(i)
		}
	}

	tgtVal := reflect.ValueOf(target)
	if tgtVal.Kind() != reflect.Ptr {
		panic("target harus berupa pointer ke struct")
	}
	tgtVal = tgtVal.Elem()
	tgtType := tgtVal.Type()

	// 2. Isi struct tujuan dengan mencocokkan tag 'column'
	for i := 0; i < tgtVal.NumField(); i++ {
		columnTag := tgtType.Field(i).Tag.Get(target_tag)
		if columnTag == "" {
			continue
		}

		// Jika ada data di map yang cocok dengan tag column, pindahkan nilainya
		if srcFieldVal, tersedia := jsonMap[columnTag]; tersedia {
			targetField := tgtVal.Field(i)

			// Pastikan tipe datanya sama atau bisa saling di-assign untuk menghindari panic
			if targetField.CanSet() && srcFieldVal.Type().AssignableTo(targetField.Type()) {
				targetField.Set(srcFieldVal)
			}
		}
	}
}
