package usecases

import (
	"cargo-erp-backend/internal/domain"
	"cargo-erp-backend/internal/handlers/dto"
	"cargo-erp-backend/pkg/datatable"
	"cargo-erp-backend/pkg/helpers"
	"fmt"
	"reflect"
	"time"

	"gorm.io/gorm"
)

type PostalCodeUsecaseInterface interface {
	GetAll(filter dto.PostalCodeFilterAllRequest) ([]domain.PostalCode, int64, error)
	GetList(dto.DataTableRequest) (dto.DataTableResponse, error)
	GetByID(id string) (domain.PostalCode, error)
	Create(postalCode string, provinceID string, cityID string, districtID string, subdistrictID string, createdBy string, merchantid *string) (domain.PostalCode, error)
	Update(id string, postalCode string, provinceID string, cityID string, districtID string, subdistrictID string, updatedBy string) (domain.PostalCode, error)
	Delete(id string) error
}

type PostalCodeUsecase struct {
	Helper   helpers.HelperInterface
	AuthUser domain.User
}

func NewPostalCodeUsecase(helper helpers.HelperInterface, authUser domain.User) PostalCodeUsecaseInterface {
	return &PostalCodeUsecase{Helper: helper, AuthUser: authUser}
}

func (u *PostalCodeUsecase) GetAll(filter dto.PostalCodeFilterAllRequest) ([]domain.PostalCode, int64, error) {
	var postalCodes []domain.PostalCode
	var total int64

	db := u.Helper.GetDB("slave")
	tx := db.Model(&postalCodes)
	if u.AuthUser.UsersRole_Relation == nil || u.AuthUser.UsersRole_Relation.RoleKey != "SPM" {
		tx = tx.Where(db.Where("merchant_id IS NULL").Or("merchant_id=?", u.AuthUser.MerchantID))
	}
	v := reflect.ValueOf(filter)
	t := reflect.TypeOf(filter)

	for i := 0; i < v.NumField(); i++ {
		if v.Field(i).String() != "" {
			tagKey := t.Field(i).Tag.Get("form")
			tx = tx.Where(fmt.Sprintf("%v=?", tagKey), v.Field(i))
		}
	}
	tx.Model(&domain.PostalCode{}).Count(&total)
	tx.Order("postal_code ASC")
	if err := tx.Find(&postalCodes).Error; err != nil {
		return nil, 0, err
	}
	return postalCodes, total, nil
}

func (u *PostalCodeUsecase) GetList(req dto.DataTableRequest) (dto.DataTableResponse, error) {
	var response dto.DataTableResponse
	db := u.Helper.GetDB("slave")
	tx := db.Table("postal_code").
		Joins("LEFT JOIN subdistrict ON postal_code.subdistrict_id=subdistrict.id").
		Joins("LEFT JOIN district ON postal_code.district_id=district.id").
		Joins("LEFT JOIN city ON postal_code.city_id=city.id").
		Joins("LEFT JOIN province ON postal_code.province_id=province.id").
		Joins("LEFT JOIN merchant ON postal_code.merchant_id=merchant.id")
	if u.AuthUser.UsersRole_Relation == nil || u.AuthUser.UsersRole_Relation.RoleKey != "SPM" {
		tx = tx.Where("postal_code.merchant_id IS NULL OR postal_code.merchant_id=?", u.AuthUser.MerchantID)
	}
	dt := datatable.NewDatatable(tx, req)
	coldef := make([]dto.DataTableColDef, 0)
	coldef = append(coldef, dto.DataTableColDef{
		Field: "postal_code.postal_code",
		Alias: "postal_code",
	})
	coldef = append(coldef, dto.DataTableColDef{
		Field: "subdistrict.name",
		Alias: "subdistrict_name",
	})
	coldef = append(coldef, dto.DataTableColDef{
		Field: "district.name",
		Alias: "district_name",
	})
	coldef = append(coldef, dto.DataTableColDef{
		Field: "city.name",
		Alias: "city_name",
	})
	coldef = append(coldef, dto.DataTableColDef{
		Field: "province.name",
		Alias: "province_name",
	})
	coldef = append(coldef, dto.DataTableColDef{
		Field: "postal_code.province_id",
		Alias: "province_id",
	})
	coldef = append(coldef, dto.DataTableColDef{
		Field: "postal_code.city_id",
		Alias: "city_id",
	})
	coldef = append(coldef, dto.DataTableColDef{
		Field: "postal_code.district_id",
		Alias: "district_id",
	})
	coldef = append(coldef, dto.DataTableColDef{
		Field: "postal_code.subdistrict_id",
		Alias: "subdistrict_id",
	})
	coldef = append(coldef, dto.DataTableColDef{
		Field: "merchant.id",
		Alias: "merchant_id",
	})
	coldef = append(coldef, dto.DataTableColDef{
		Field: "postal_code.id",
		Alias: "id",
	})

	response = dt.Render(coldef)
	return response, nil
}

func (u *PostalCodeUsecase) GetByID(id string) (domain.PostalCode, error) {
	var postalCode domain.PostalCode
	db := u.Helper.GetDB("slave")
	if u.AuthUser.UsersRole_Relation == nil || u.AuthUser.UsersRole_Relation.RoleKey != "SPM" {
		db = db.Where("merchant_id IS NULL OR merchant_id=?", u.AuthUser.MerchantID)
	}
	if err := db.Where("id = ?", id).First(&postalCode).Error; err != nil {
		return domain.PostalCode{}, err
	}
	return postalCode, nil
}

func (u *PostalCodeUsecase) Create(postalCode string, provinceID string, cityID string, districtID string, subdistrictID string, createdBy string, merchantid *string) (domain.PostalCode, error) {
	now := time.Now()

	db := u.Helper.GetDB("master")
	var pc domain.PostalCode
	err := db.Transaction(func(tx *gorm.DB) error {
		pc = domain.PostalCode{
			PostalCode:    &postalCode,
			ProvinceID:    &provinceID,
			CityID:        &cityID,
			DistrictID:    &districtID,
			SubdistrictID: &subdistrictID,
			MerchantID:    merchantid,
			CreatedBy:     &createdBy,
			CreatedOn:     &now,
			UpdatedBy:     &createdBy,
			UpdatedOn:     strPtr(now.Format("2006-01-02 15:04:05.999999-07:00")),
		}
		return tx.Create(&pc).Error
	})
	if err != nil {
		return domain.PostalCode{}, err
	}
	logUsecase := NewActivityLogUsecase(u.Helper, u.AuthUser)
	logUsecase.LogCreate("postal_code", pc, merchantid)
	return pc, nil
}

func (u *PostalCodeUsecase) Update(id string, postalCode string, provinceID string, cityID string, districtID string, subdistrictID string, updatedBy string) (domain.PostalCode, error) {
	var pc domain.PostalCode
	var oldPC domain.PostalCode
	db := u.Helper.GetDB("master")
	err := db.Transaction(func(tx *gorm.DB) error {
		q := tx
		if u.AuthUser.UsersRole_Relation == nil || u.AuthUser.UsersRole_Relation.RoleKey != "SPM" {
			q = tx.Where("merchant_id=?", u.AuthUser.MerchantID)
		}
		if err := q.Where("id = ?", id).First(&pc).Error; err != nil {
			return err
		}
		oldPC = pc
		now := time.Now()
		pc.PostalCode = &postalCode
		pc.ProvinceID = &provinceID
		pc.CityID = &cityID
		pc.DistrictID = &districtID
		pc.SubdistrictID = &subdistrictID
		pc.UpdatedBy = &updatedBy
		pc.UpdatedOn = strPtr(now.Format("2006-01-02 15:04:05.999999-07:00"))
		return tx.Save(&pc).Error
	})
	if err != nil {
		return domain.PostalCode{}, err
	}
	var merchantID *string
	if u.AuthUser.UsersRole_Relation == nil || u.AuthUser.UsersRole_Relation.RoleKey != "SPM" {
		merchantID = u.AuthUser.MerchantID
	}
	logUsecase := NewActivityLogUsecase(u.Helper, u.AuthUser)
	logUsecase.LogUpdate("postal_code", oldPC, pc, merchantID)
	return pc, nil
}

func (u *PostalCodeUsecase) Delete(id string) error {
	db := u.Helper.GetDB("master")
	return db.Transaction(func(tx *gorm.DB) error {
		var pc domain.PostalCode
		q := tx
		if u.AuthUser.UsersRole_Relation == nil || u.AuthUser.UsersRole_Relation.RoleKey != "SPM" {
			q = tx.Where("merchant_id=?", u.AuthUser.MerchantID)
		}
		if err := q.Where("id = ?", id).First(&pc).Error; err != nil {
			return err
		}
		var merchantID *string
		if u.AuthUser.UsersRole_Relation == nil || u.AuthUser.UsersRole_Relation.RoleKey != "SPM" {
			merchantID = u.AuthUser.MerchantID
		}
		logUsecase := NewActivityLogUsecase(u.Helper, u.AuthUser)
		logUsecase.LogDelete("postal_code", id, pc, merchantID)
		if err := tx.Delete(&domain.PostalCode{}, "id = ?", id).Error; err != nil {
			return err
		}
		return nil
	})
}
