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 BasicPricingUsecaseInterface interface {
	GetAll(filter dto.BasicPricingFilterAllRequest) ([]dto.CreateBasicPricingResponse, int64, error)
	GetList(dto.DataTableRequest) (dto.DataTableResponse, error)
	GetByID(id string) (dto.CreateBasicPricingResponse, error)
	Create(req dto.CreateBasicPricingRequest, createdBy string, merchantid *string) (dto.CreateBasicPricingResponse, error)
	Update(id string, req dto.CreateBasicPricingRequest, updatedBy string) (dto.CreateBasicPricingResponse, error)
	Delete(id string) error
	GetMatch(id string) *dto.CreateBasicPricingResponse
}

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

func NewBasicPricingUsecase(helper helpers.HelperInterface, authUser domain.User) BasicPricingUsecaseInterface {
	return &BasicPricingUsecase{Helper: helper, AuthUser: authUser}
}

func (u *BasicPricingUsecase) loadComponents(pricingIDs []string) (map[string][]domain.BasicPricingComponent, error) {
	if len(pricingIDs) == 0 {
		return make(map[string][]domain.BasicPricingComponent), nil
	}
	var components []domain.BasicPricingComponent
	db := u.Helper.GetDB("slave")
	if err := db.Where("basic_pricing_id IN ?", pricingIDs).Find(&components).Error; err != nil {
		return nil, err
	}
	componentsMap := make(map[string][]domain.BasicPricingComponent)
	for _, c := range components {
		if c.BasicPricingID != nil {
			componentsMap[*c.BasicPricingID] = append(componentsMap[*c.BasicPricingID], c)
		}
	}
	return componentsMap, nil
}

func (u *BasicPricingUsecase) toResponse(pricing domain.BasicPricing, components []domain.BasicPricingComponent) dto.CreateBasicPricingResponse {
	resp := dto.CreateBasicPricingResponse{
		ID:                       pricing.ID,
		ProvinceSourceID:         pricing.ProvinceSourceID,
		CitySourceID:             pricing.CitySourceID,
		DistrictSourceID:         pricing.DistrictSourceID,
		SubdistrictSourceID:      pricing.SubdistrictSourceID,
		PostalCodeSourceID:       pricing.PostalCodeSourceID,
		ProvinceDestinationID:    pricing.ProvinceDestinationID,
		CityDestinationID:        pricing.CityDestinationID,
		DistrictDestinationID:    pricing.DistrictDestinationID,
		SubdistrictDestinationID: pricing.SubdistrictDestinationID,
		PostalCodeDestinationID:  pricing.PostalCodeDestinationID,
		PackageTypeID:            pricing.PackageTypeID,
		ModaID:                   pricing.ModaID,
		LeadTime:                 pricing.LeadTime,
		MinimumWeight:            pricing.MinimumWeight,
		MerchantID:               pricing.MerchantID,
	}
	resp.Components = make([]dto.BasicPricingComponentResponse, 0, len(components))
	for _, c := range components {
		resp.Components = append(resp.Components, dto.BasicPricingComponentResponse{
			ID:              c.ID,
			CostComponentID: c.CostComponentID,
			Value:           c.Value,
		})
	}
	return resp
}
func (u *BasicPricingUsecase) GetMatch(id string) *dto.CreateBasicPricingResponse {
	var pricings domain.BasicPricing

	db := u.Helper.GetDB("slave")
	tx := db.Model(&pricings).Where("basic_pricing.id = ?", id)
	if u.AuthUser.UsersRole_Relation == nil || u.AuthUser.UsersRole_Relation.RoleKey != "SPM" {
		tx = tx.Where("basic_pricing.merchant_id = ?", u.AuthUser.MerchantID)
	}

	if err := tx.First(&pricings).Error; err != nil {
		return nil
	}

	pricingIDs := make([]string, 0)
	pricingIDs = append(pricingIDs, pricings.ID)
	componentsMap, err := u.loadComponents(pricingIDs)
	if err != nil {
		return nil
	}

	responses := u.toResponse(pricings, componentsMap[pricings.ID])
	return &responses
}
func (u *BasicPricingUsecase) GetAll(filter dto.BasicPricingFilterAllRequest) ([]dto.CreateBasicPricingResponse, int64, error) {
	var pricings []domain.BasicPricing
	var total int64

	db := u.Helper.GetDB("slave")
	tx := db.Model(&pricings)
	if u.AuthUser.UsersRole_Relation == nil || u.AuthUser.UsersRole_Relation.RoleKey != "SPM" {
		tx = tx.Where("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.BasicPricing{}).Count(&total)
	tx.Order("id ASC")
	if err := tx.Find(&pricings).Error; err != nil {
		return nil, 0, err
	}

	pricingIDs := make([]string, 0, len(pricings))
	for _, p := range pricings {
		pricingIDs = append(pricingIDs, p.ID)
	}
	componentsMap, err := u.loadComponents(pricingIDs)
	if err != nil {
		return nil, 0, err
	}

	responses := make([]dto.CreateBasicPricingResponse, 0, len(pricings))
	for _, p := range pricings {
		components := componentsMap[p.ID]
		responses = append(responses, u.toResponse(p, components))
	}
	return responses, total, nil
}

func (u *BasicPricingUsecase) GetList(req dto.DataTableRequest) (dto.DataTableResponse, error) {
	var response dto.DataTableResponse
	db := u.Helper.GetDB("slave")
	tx := db.Table("basic_pricing").
		Joins("LEFT JOIN province AS ps ON basic_pricing.province_source_id=ps.id").
		Joins("LEFT JOIN city AS cs ON basic_pricing.city_source_id=cs.id").
		Joins("LEFT JOIN province AS pd ON basic_pricing.province_destination_id=pd.id").
		Joins("LEFT JOIN city AS cd ON basic_pricing.city_destination_id=cd.id").
		Joins("LEFT JOIN merchant ON basic_pricing.merchant_id=merchant.id")
	if u.AuthUser.UsersRole_Relation == nil || u.AuthUser.UsersRole_Relation.RoleKey != "SPM" {
		tx = tx.Where("basic_pricing.merchant_id=?", u.AuthUser.MerchantID)
	}
	dt := datatable.NewDatatable(tx, req)
	coldef := make([]dto.DataTableColDef, 0)
	coldef = append(coldef, dto.DataTableColDef{
		Field: "basic_pricing.id",
		Alias: "id",
	})

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

func (u *BasicPricingUsecase) GetByID(id string) (dto.CreateBasicPricingResponse, error) {
	var pricing domain.BasicPricing
	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(&pricing).Error; err != nil {
		return dto.CreateBasicPricingResponse{}, err
	}

	var components []domain.BasicPricingComponent
	if err := db.Where("basic_pricing_id = ?", id).Find(&components).Error; err != nil {
		return dto.CreateBasicPricingResponse{}, err
	}

	return u.toResponse(pricing, components), nil
}

func (u *BasicPricingUsecase) Create(req dto.CreateBasicPricingRequest, createdBy string, merchantid *string) (dto.CreateBasicPricingResponse, error) {
	now := time.Now()

	db := u.Helper.GetDB("master")
	var pricing domain.BasicPricing
	var savedComponents []domain.BasicPricingComponent

	err := db.Transaction(func(tx *gorm.DB) error {
		pricing = domain.BasicPricing{
			ProvinceSourceID:         strPtr(req.ProvinceSourceID),
			CitySourceID:             strPtr(req.CitySourceID),
			DistrictSourceID:         strPtr(req.DistrictSourceID),
			SubdistrictSourceID:      strPtr(req.SubdistrictSourceID),
			PostalCodeSourceID:       strPtr(req.PostalCodeSourceID),
			ProvinceDestinationID:    strPtr(req.ProvinceDestinationID),
			CityDestinationID:        strPtr(req.CityDestinationID),
			DistrictDestinationID:    strPtr(req.DistrictDestinationID),
			SubdistrictDestinationID: strPtr(req.SubdistrictDestinationID),
			PostalCodeDestinationID:  strPtr(req.PostalCodeDestinationID),
			PackageTypeID:            strPtr(req.PackageTypeID),
			ModaID:                   strPtr(req.ModaID),
			LeadTime:                 strPtr(req.LeadTime),
			MinimumWeight:            float32Ptr(req.MinimumWeight),
			MerchantID:               merchantid,
			CreatedBy:                &createdBy,
			CreatedOn:                &now,
			UpdatedBy:                &createdBy,
			UpdatedOn:                &now,
		}
		if err := tx.Create(&pricing).Error; err != nil {
			return err
		}

		for _, comp := range req.Components {
			c := domain.BasicPricingComponent{
				BasicPricingID:  &pricing.ID,
				CostComponentID: &comp.CostComponentID,
				Value:           &comp.Value,
			}
			if err := tx.Create(&c).Error; err != nil {
				return err
			}
			savedComponents = append(savedComponents, c)
		}
		return nil
	})
	if err != nil {
		return dto.CreateBasicPricingResponse{}, err
	}
	logUsecase := NewActivityLogUsecase(u.Helper, u.AuthUser)
	logUsecase.LogCreate("basic_pricing", pricing, merchantid)
	return u.toResponse(pricing, savedComponents), nil
}

func (u *BasicPricingUsecase) Update(id string, req dto.CreateBasicPricingRequest, updatedBy string) (dto.CreateBasicPricingResponse, error) {
	db := u.Helper.GetDB("master")
	var pricing domain.BasicPricing
	var oldPricing domain.BasicPricing
	var savedComponents []domain.BasicPricingComponent

	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(&pricing).Error; err != nil {
			return err
		}
		oldPricing = pricing
		now := time.Now()
		pricing.ProvinceSourceID = strPtr(req.ProvinceSourceID)
		pricing.CitySourceID = strPtr(req.CitySourceID)
		pricing.DistrictSourceID = strPtr(req.DistrictSourceID)
		pricing.SubdistrictSourceID = strPtr(req.SubdistrictSourceID)
		pricing.PostalCodeSourceID = strPtr(req.PostalCodeSourceID)
		pricing.ProvinceDestinationID = strPtr(req.ProvinceDestinationID)
		pricing.CityDestinationID = strPtr(req.CityDestinationID)
		pricing.DistrictDestinationID = strPtr(req.DistrictDestinationID)
		pricing.SubdistrictDestinationID = strPtr(req.SubdistrictDestinationID)
		pricing.PostalCodeDestinationID = strPtr(req.PostalCodeDestinationID)
		pricing.PackageTypeID = strPtr(req.PackageTypeID)
		pricing.ModaID = strPtr(req.ModaID)
		pricing.LeadTime = strPtr(req.LeadTime)
		pricing.MinimumWeight = float32Ptr(req.MinimumWeight)
		pricing.UpdatedBy = &updatedBy
		pricing.UpdatedOn = &now

		if err := tx.Save(&pricing).Error; err != nil {
			return err
		}

		if err := tx.Where("basic_pricing_id = ?", id).Delete(&domain.BasicPricingComponent{}).Error; err != nil {
			return err
		}

		for _, comp := range req.Components {
			c := domain.BasicPricingComponent{
				BasicPricingID:  &pricing.ID,
				CostComponentID: &comp.CostComponentID,
				Value:           &comp.Value,
			}
			if err := tx.Create(&c).Error; err != nil {
				return err
			}
			savedComponents = append(savedComponents, c)
		}
		return nil
	})
	if err != nil {
		return dto.CreateBasicPricingResponse{}, 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("basic_pricing", oldPricing, pricing, merchantID)
	return u.toResponse(pricing, savedComponents), nil
}

func (u *BasicPricingUsecase) Delete(id string) error {
	db := u.Helper.GetDB("master")
	return 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)
		}
		var pricing domain.BasicPricing
		if err := q.Where("id = ?", id).First(&pricing).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("basic_pricing", id, pricing, merchantID)
		if err := tx.Where("basic_pricing_id = ?", id).Delete(&domain.BasicPricingComponent{}).Error; err != nil {
			return err
		}
		if err := tx.Delete(&pricing).Error; err != nil {
			return err
		}
		return nil
	})
}

func strPtr(s string) *string {
	if s == "" {
		return nil
	}
	return &s
}

func float32Ptr(f float32) *float32 {
	if f == 0 {
		return nil
	}
	return &f
}
