package dto

import (
	"fmt"
	"math"
	"sort"
	"strings"
	"time"

	entity "gitlab.com/mbugroup/lti-api.git/internal/entities"
	approvalDTO "gitlab.com/mbugroup/lti-api.git/internal/modules/approvals/dto"
	productwarehouseDTO "gitlab.com/mbugroup/lti-api.git/internal/modules/inventory/product-warehouses/dto"
	customerDTO "gitlab.com/mbugroup/lti-api.git/internal/modules/master/customers/dto"
	warehouseDTO "gitlab.com/mbugroup/lti-api.git/internal/modules/master/warehouses/dto"
	userDTO "gitlab.com/mbugroup/lti-api.git/internal/modules/users/dto"
)

type MarketingRelationDTO struct {
	Id       uint      `json:"id"`
	SoNumber string    `json:"so_number"`
	DoNumber *string   `json:"do_number"`
	SoDate   time.Time `json:"so_date"`
	Notes    string    `json:"notes,omitempty"`
}

type MarketingListDTO struct {
	MarketingRelationDTO
	Customer       customerDTO.CustomerRelationDTO `json:"customer"`
	SalesPerson    userDTO.UserRelationDTO         `json:"sales_person"`
	SoDocs         string                          `json:"so_docs"`
	SalesOrder     []DeliveryMarketingProductDTO   `json:"sales_order"`
	CreatedUser    userDTO.UserRelationDTO         `json:"created_user"`
	CreatedAt      time.Time                       `json:"created_at"`
	UpdatedAt      time.Time                       `json:"updated_at"`
	LatestApproval approvalDTO.ApprovalRelationDTO `json:"latest_approval"`
}

type MarketingDetailDTO struct {
	MarketingRelationDTO
	Customer       customerDTO.CustomerRelationDTO `json:"customer"`
	SalesPerson    userDTO.UserRelationDTO         `json:"sales_person"`
	SoDocs         string                          `json:"so_docs"`
	SalesOrder     []DeliveryMarketingProductDTO   `json:"sales_order"`
	DeliveryOrder  []DeliveryGroupDTO              `json:"delivery_order"`
	CreatedUser    userDTO.UserRelationDTO         `json:"created_user"`
	CreatedAt      time.Time                       `json:"created_at"`
	UpdatedAt      time.Time                       `json:"updated_at"`
	LatestApproval approvalDTO.ApprovalRelationDTO `json:"latest_approval"`
}

type MarketingDeliveryProductDTO struct {
	Id                  uint                                          `json:"id"`
	MarketingProductId  uint                                          `json:"marketing_product_id"`
	Qty                 float64                                       `json:"qty"`
	UnitPrice           float64                                       `json:"unit_price"`
	TotalWeight         float64                                       `json:"total_weight"`
	AvgWeight           float64                                       `json:"avg_weight"`
	TotalPrice          float64                                       `json:"total_price"`
	DeliveryDate        *time.Time                                    `json:"delivery_date"`
	VehicleNumber       string                                        `json:"vehicle_number"`
	ConvertionUnit      *string                                       `json:"-"`
	WeightPerConvertion *float64                                      `json:"-"`
	ProductWarehouse    *productwarehouseDTO.ProductWarehousNestedDTO `json:"product_warehouse,omitempty"`
}

type DeliveryItemDTO struct {
	MarketingProductId  uint                                          `json:"marketing_product_id"`
	ProductWarehouse    *productwarehouseDTO.ProductWarehousNestedDTO `json:"product_warehouse"`
	Qty                 float64                                       `json:"qty"`
	UnitPrice           float64                                       `json:"unit_price"`
	TotalWeight         float64                                       `json:"total_weight"`
	AvgWeight           float64                                       `json:"avg_weight"`
	WeightPerConvertion *float64                                      `json:"weight_per_convertion"`
	TotalPeti           *float64                                      `json:"total_peti"`
	TotalPrice          float64                                       `json:"total_price"`
	VehicleNumber       string                                        `json:"vehicle_number"`
}

type DeliveryGroupDTO struct {
	DoNumber     string                             `json:"do_number"`
	DeliveryDate *time.Time                         `json:"delivery_date"`
	Warehouse    *warehouseDTO.WarehouseRelationDTO `json:"warehouse,omitempty"`
	Deliveries   []DeliveryItemDTO                  `json:"deliveries"`
}

type DeliveryMarketingProductDTO struct {
	Id                  uint                                          `json:"id"`
	MarketingId         uint                                          `json:"marketing_id"`
	ProductWarehouseId  uint                                          `json:"product_warehouse_id"`
	MarketingType       string                                        `json:"marketing_type"`
	Qty                 float64                                       `json:"qty"`
	UnitPrice           float64                                       `json:"unit_price"`
	AvgWeight           float64                                       `json:"avg_weight"`
	TotalWeight         float64                                       `json:"total_weight"`
	TotalPrice          float64                                       `json:"total_price"`
	ConvertionUnit      *string                                       `json:"convertion_unit,omitempty"`
	WeightPerConvertion *float64                                      `json:"weight_per_convertion,omitempty"`
	TotalPeti           *float64                                      `json:"total_peti,omitempty"`
	Week                *int                                          `json:"week,omitempty"`
	ProductWarehouse    *productwarehouseDTO.ProductWarehousNestedDTO `json:"product_warehouse,omitempty"`
	VehicleNumber       string                                        `json:"vehicle_number,omitempty"`
}

func ToMarketingRelationDTO(marketing *entity.Marketing) MarketingRelationDTO {
	var doNumber *string
	if doNumbers := collectDoNumbers(marketing); len(doNumbers) > 0 {
		value := doNumbers[0]
		doNumber = &value
	}

	return MarketingRelationDTO{
		Id:       marketing.Id,
		SoNumber: marketing.SoNumber,
		DoNumber: doNumber,
		SoDate:   marketing.SoDate,
		Notes:    marketing.Notes,
	}
}

func ToDeliveryMarketingProductDTO(e entity.MarketingProduct, marketingType string) DeliveryMarketingProductDTO {
	var productWarehouse *productwarehouseDTO.ProductWarehousNestedDTO
	if e.ProductWarehouse.Id != 0 {
		mapped := productwarehouseDTO.ToProductWarehouseNestedDTO(e.ProductWarehouse)
		productWarehouse = &mapped
	}

	// Calculate total_peti only for TELUR marketing type
	var totalPeti *float64
	if marketingType == "TELUR" && e.ConvertionUnit != nil && *e.ConvertionUnit == "PETI" && e.WeightPerConvertion != nil && *e.WeightPerConvertion > 0 {
		calculated := math.Floor(e.TotalWeight / *e.WeightPerConvertion)
		totalPeti = &calculated
	}

	return DeliveryMarketingProductDTO{
		Id:                  e.Id,
		MarketingId:         e.MarketingId,
		ProductWarehouseId:  e.ProductWarehouseId,
		MarketingType:       marketingType,
		Qty:                 e.Qty,
		UnitPrice:           e.UnitPrice,
		AvgWeight:           e.AvgWeight,
		TotalWeight:         e.TotalWeight,
		TotalPrice:          e.TotalPrice,
		ConvertionUnit:      e.ConvertionUnit,
		WeightPerConvertion: e.WeightPerConvertion,
		TotalPeti:           totalPeti,
		Week:                e.Week,
		ProductWarehouse:    productWarehouse,
		VehicleNumber:       getVehicleNumber(e),
	}
}

func ToMarketingDeliveryProductDTO(e entity.MarketingDeliveryProduct) MarketingDeliveryProductDTO {
	return MarketingDeliveryProductDTO{
		Id:                  e.Id,
		MarketingProductId:  e.MarketingProductId,
		Qty:                 e.UsageQty + e.PendingQty,
		UnitPrice:           e.UnitPrice,
		TotalWeight:         e.TotalWeight,
		AvgWeight:           e.AvgWeight,
		TotalPrice:          e.TotalPrice,
		DeliveryDate:        e.DeliveryDate,
		VehicleNumber:       e.VehicleNumber,
		WeightPerConvertion: e.WeightPerConvertion,
	}
}

func ToMarketingListDTO(marketing *entity.Marketing, deliveryProducts []entity.MarketingDeliveryProduct) MarketingListDTO {
	var createdUser userDTO.UserRelationDTO
	if marketing.CreatedUser.Id != 0 {
		mapped := userDTO.ToUserRelationDTO(marketing.CreatedUser)
		createdUser = mapped
	}

	var customer customerDTO.CustomerRelationDTO
	if marketing.Customer.Id != 0 {
		mapped := customerDTO.ToCustomerRelationDTO(marketing.Customer)
		customer = mapped
	}

	var salesPerson userDTO.UserRelationDTO
	if marketing.SalesPerson.Id != 0 {
		mapped := userDTO.ToUserRelationDTO(marketing.SalesPerson)
		salesPerson = mapped
	}

	var latestApproval approvalDTO.ApprovalRelationDTO
	if marketing.LatestApproval != nil {
		mapped := approvalDTO.ToApprovalDTO(*marketing.LatestApproval)
		latestApproval = mapped
	}

	var salesOrderProducts []DeliveryMarketingProductDTO
	if len(marketing.Products) > 0 {
		salesOrderProducts = make([]DeliveryMarketingProductDTO, len(marketing.Products))
		for i, product := range marketing.Products {
			salesOrderProducts[i] = ToDeliveryMarketingProductDTO(product, marketing.MarketingType)
		}
	}
	return MarketingListDTO{
		MarketingRelationDTO: ToMarketingRelationDTO(marketing),
		Customer:             customer,
		SalesPerson:          salesPerson,
		SoDocs:               marketing.SoDocs,
		SalesOrder:           salesOrderProducts,
		CreatedUser:          createdUser,
		CreatedAt:            marketing.CreatedAt,
		UpdatedAt:            marketing.UpdatedAt,
		LatestApproval:       latestApproval,
	}
}

func ToMarketingDetailDTO(marketing *entity.Marketing, deliveryProducts []entity.MarketingDeliveryProduct) MarketingDetailDTO {
	var createdUser userDTO.UserRelationDTO
	if marketing.CreatedUser.Id != 0 {
		mapped := userDTO.ToUserRelationDTO(marketing.CreatedUser)
		createdUser = mapped
	}

	var customer customerDTO.CustomerRelationDTO
	if marketing.Customer.Id != 0 {
		mapped := customerDTO.ToCustomerRelationDTO(marketing.Customer)
		customer = mapped
	}

	var salesPerson userDTO.UserRelationDTO
	if marketing.SalesPerson.Id != 0 {
		mapped := userDTO.ToUserRelationDTO(marketing.SalesPerson)
		salesPerson = mapped
	}

	var salesOrderProducts []DeliveryMarketingProductDTO
	if len(marketing.Products) > 0 {
		salesOrderProducts = make([]DeliveryMarketingProductDTO, len(marketing.Products))
		for i, product := range marketing.Products {
			salesOrderProducts[i] = ToDeliveryMarketingProductDTO(product, marketing.MarketingType)
		}
	}

	var deliveryProductsDTOs []MarketingDeliveryProductDTO
	if len(deliveryProducts) > 0 {
		deliveryProductsDTOs = make([]MarketingDeliveryProductDTO, len(deliveryProducts))
		for i, dp := range deliveryProducts {
			deliveryProductsDTOs[i] = ToMarketingDeliveryProductDTO(dp)
		}
		deliveryProductsDTOs = enrichDeliveryProductDTOsWithWarehouse(deliveryProductsDTOs, marketing)
	}

	deliveryGroups := groupDeliveryProducts(deliveryProductsDTOs, marketing.SoNumber)

	var latestApproval approvalDTO.ApprovalRelationDTO
	if marketing.LatestApproval != nil {
		mapped := approvalDTO.ToApprovalDTO(*marketing.LatestApproval)
		latestApproval = mapped
	}
	return MarketingDetailDTO{
		MarketingRelationDTO: ToMarketingRelationDTO(marketing),
		SoDocs:               marketing.SoDocs,
		Customer:             customer,
		SalesPerson:          salesPerson,
		SalesOrder:           salesOrderProducts,
		DeliveryOrder:        deliveryGroups,
		CreatedUser:          createdUser,
		CreatedAt:            marketing.CreatedAt,
		UpdatedAt:            marketing.UpdatedAt,
		LatestApproval:       latestApproval,
	}
}

func ToMarketingListDTOs(marketings []entity.Marketing) []MarketingListDTO {
	result := make([]MarketingListDTO, len(marketings))
	for i, m := range marketings {
		result[i] = ToMarketingListDTO(&m, []entity.MarketingDeliveryProduct{})
	}
	return result
}

func enrichDeliveryProductDTOsWithWarehouse(deliveryProductDTOs []MarketingDeliveryProductDTO, marketing *entity.Marketing) []MarketingDeliveryProductDTO {
	if len(deliveryProductDTOs) == 0 || marketing == nil || len(marketing.Products) == 0 {
		return deliveryProductDTOs
	}

	productMap := make(map[uint]*entity.MarketingProduct)
	for i := range marketing.Products {
		productMap[marketing.Products[i].Id] = &marketing.Products[i]
	}

	for i := range deliveryProductDTOs {
		if product, exists := productMap[deliveryProductDTOs[i].MarketingProductId]; exists {
			if product.ProductWarehouse.Id != 0 {
				mapped := productwarehouseDTO.ToProductWarehouseNestedDTO(product.ProductWarehouse)
				deliveryProductDTOs[i].ProductWarehouse = &mapped
			}
			deliveryProductDTOs[i].ConvertionUnit = product.ConvertionUnit
		}
	}

	return deliveryProductDTOs
}

func groupDeliveryProducts(products []MarketingDeliveryProductDTO, soNumber string) []DeliveryGroupDTO {
	groupMap := make(map[string]*DeliveryGroupDTO)

	for _, product := range products {
		if product.DeliveryDate == nil {
			continue
		}

		var warehouseId uint
		var warehouseName string
		if product.ProductWarehouse != nil {
			warehouseId = product.ProductWarehouse.Warehouse.Id
			warehouseName = product.ProductWarehouse.Warehouse.Name
		}

		key := fmt.Sprintf("%d_%s", warehouseId, product.DeliveryDate.Format("2006-01-02"))

		group, exists := groupMap[key]
		if !exists {
			group = &DeliveryGroupDTO{
				DeliveryDate: product.DeliveryDate,
				Warehouse: &warehouseDTO.WarehouseRelationDTO{
					Id:   warehouseId,
					Name: warehouseName,
				},
				Deliveries: []DeliveryItemDTO{},
			}
			groupMap[key] = group
		}

		deliveryItem := DeliveryItemDTO{
			MarketingProductId:  product.MarketingProductId,
			ProductWarehouse:    product.ProductWarehouse,
			Qty:                 product.Qty,
			UnitPrice:           product.UnitPrice,
			TotalWeight:         product.TotalWeight,
			AvgWeight:           product.AvgWeight,
			WeightPerConvertion: product.WeightPerConvertion,
			TotalPrice:          product.TotalPrice,
			VehicleNumber:       product.VehicleNumber,
		}
		if product.ConvertionUnit != nil &&
			strings.EqualFold(*product.ConvertionUnit, "PETI") &&
			product.WeightPerConvertion != nil &&
			*product.WeightPerConvertion > 0 {
			totalPeti := product.TotalWeight / *product.WeightPerConvertion
			deliveryItem.TotalPeti = &totalPeti
		}
		group.Deliveries = append(group.Deliveries, deliveryItem)
	}

	var groups []DeliveryGroupDTO
	for _, group := range groupMap {
		groups = append(groups, *group)
	}

	sort.Slice(groups, func(i, j int) bool {
		if groups[i].DeliveryDate == nil || groups[j].DeliveryDate == nil {
			return false
		}
		return groups[i].DeliveryDate.Before(*groups[j].DeliveryDate)
	})

	for i := range groups {
		groups[i].DoNumber = GenerateDeliveryOrderNumber(soNumber, groups[i].DeliveryDate, groups[i].Warehouse.Id)
	}

	return groups
}

func GenerateDeliveryOrderNumber(soNumber string, deliveryDate *time.Time, warehouseId uint) string {
	numberPrefix := soNumber
	if strings.HasPrefix(strings.ToUpper(strings.TrimSpace(soNumber)), "SO-") {
		numberPrefix = "DO-" + soNumber[3:]
	}
	return numberPrefix
}

func collectDoNumbers(marketing *entity.Marketing) []string {
	if marketing == nil || len(marketing.Products) == 0 {
		return nil
	}

	seen := make(map[string]struct{})
	for _, product := range marketing.Products {
		if product.DeliveryProduct == nil || product.DeliveryProduct.DeliveryDate == nil {
			continue
		}
		warehouseID := product.ProductWarehouse.WarehouseId
		if warehouseID == 0 && product.ProductWarehouse.Warehouse.Id != 0 {
			warehouseID = product.ProductWarehouse.Warehouse.Id
		}
		if warehouseID == 0 {
			continue
		}

		doNumber := GenerateDeliveryOrderNumber(marketing.SoNumber, product.DeliveryProduct.DeliveryDate, warehouseID)
		if doNumber != "" {
			seen[doNumber] = struct{}{}
		}
	}

	if len(seen) == 0 {
		return nil
	}
	result := make([]string, 0, len(seen))
	for value := range seen {
		result = append(result, value)
	}
	sort.Strings(result)
	return result
}

func getVehicleNumber(e entity.MarketingProduct) string {
	if e.DeliveryProduct != nil && e.DeliveryProduct.VehicleNumber != "" {
		return e.DeliveryProduct.VehicleNumber
	}
	return ""
}
