package dto

import (
	"math"
	"strings"
	"time"

	"gitlab.com/mbugroup/lti-api.git/internal/config"
	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/adjustments/dto"
	userDTO "gitlab.com/mbugroup/lti-api.git/internal/modules/users/dto"
	"gitlab.com/mbugroup/lti-api.git/internal/utils"
	approvalutils "gitlab.com/mbugroup/lti-api.git/internal/utils/approvals"
)

// === DTO Structs ===

type RecordingProjectFlockDTO struct {
	ProjectFlockKandangId uint                            `json:"project_flock_kandang_id"`
	FlockName             string                          `json:"flock_name"`
	ProjectFlockCategory  string                          `json:"project_flock_category"`
	Period                int                             `json:"period"`
	ProductionStandart    *RecordingProductionStandardDTO `json:"production_standart,omitempty"`
	Fcr                   *RecordingFcrDTO                `json:"fcr,omitempty"`
	TotalChickQty         float64                         `json:"total_chick_qty"`
}

type RecordingProductionStandardDTO struct {
	Id              uint    `json:"id"`
	Week            int     `json:"week"`
	Name            string  `json:"name"`
	HenDayStd       float64 `json:"hen_day_std"`
	HenHouseStd     float64 `json:"hen_house_std"`
	FeedIntakeStd   float64 `json:"feed_intake_std"`
	MaxDepletionStd float64 `json:"max_depletion_std"`
	EggMassStd      float64 `json:"egg_mass_std"`
	EggWeightStd    float64 `json:"egg_weight_std"`
}

type RecordingFcrDTO struct {
	Id     uint    `json:"id"`
	Name   string  `json:"name"`
	FcrStd float64 `json:"fcr_std"`
}

type RecordingAreaDTO struct {
	Id   uint   `json:"id"`
	Name string `json:"name"`
}

type RecordingLocationDTO struct {
	Id      uint   `json:"id"`
	Name    string `json:"name"`
	Address string `json:"address"`
}

type RecordingKandangDTO struct {
	Id       uint    `json:"id"`
	Name     string  `json:"name"`
	Status   string  `json:"status"`
	Capacity float64 `json:"capacity"`
}

type RecordingWarehouseDTO struct {
	Id       uint                  `json:"id"`
	Name     string                `json:"name"`
	Area     *RecordingAreaDTO     `json:"area,omitempty"`
	Location *RecordingLocationDTO `json:"location,omitempty"`
}

type RecordingRelationDTO struct {
	Id                   uint                            `json:"id"`
	ProjectFlock         RecordingProjectFlockDTO        `json:"project_flock"`
	RecordDatetime       time.Time                       `json:"record_datetime"`
	Day                  int                             `json:"day"`
	TotalDepletionQty    float64                         `json:"total_depletion_qty"`
	TotalDepletionCumQty float64                         `json:"total_depletion_cum_qty"`
	CumDepletionRate     float64                         `json:"cum_depletion_rate"`
	DepletionRate        float64                         `json:"depletion_rate"`
	CumIntake            int                             `json:"cum_intake"`
	FcrValue             float64                         `json:"fcr_value"`
	HenDay               float64                         `json:"hen_day"`
	HenHouse             float64                         `json:"hen_house"`
	FeedIntake           float64                         `json:"feed_intake"`
	EggMass              float64                         `json:"egg_mass"`
	EggWeight            float64                         `json:"egg_weight"`
	PopulationCanChange  bool                            `json:"population_can_change"`
	TransferExecuted     *bool                           `json:"transfer_executed,omitempty"`
	IsTransition         bool                            `json:"is_transition"`
	IsLaying             bool                            `json:"is_laying"`
	Approval             approvalDTO.ApprovalRelationDTO `json:"approval"`
}

type RecordingListDTO struct {
	RecordingRelationDTO
	CreatedUser *userDTO.UserRelationDTO `json:"created_user,omitempty"`
	CreatedAt   time.Time                `json:"created_at"`
	UpdatedAt   time.Time                `json:"updated_at"`
	Kandang     *RecordingKandangDTO     `json:"kandang,omitempty"`
	Location    *RecordingLocationDTO    `json:"location,omitempty"`
}

type RecordingDetailDTO struct {
	RecordingListDTO
	ProductCategory string                  `json:"product_category"`
	Warehouse       *RecordingWarehouseDTO  `json:"warehouse,omitempty"`
	Depletions      []RecordingDepletionDTO `json:"depletions"`
	Stocks          []RecordingStockDTO     `json:"stocks"`
	Eggs            []RecordingEggDTO       `json:"eggs"`
}

type RecordingDepletionDTO struct {
	ProductWarehouseId uint                                    `json:"product_warehouse_id"`
	Qty                float64                                 `json:"qty"`
	ProductWarehouse   productWarehouseDTO.ProductWarehouseDTO `json:"product_warehouse"`
}

type RecordingStockDTO struct {
	ProductWarehouseId uint                                    `json:"product_warehouse_id"`
	UsageAmount        float64                                 `json:"usage_amount"`
	PendingQty         float64                                 `json:"pending_qty"`
	ProductWarehouse   productWarehouseDTO.ProductWarehouseDTO `json:"product_warehouse"`
}

type RecordingEggDTO struct {
	Id                 uint                                    `json:"id"`
	ProductWarehouseId uint                                    `json:"product_warehouse_id"`
	Qty                int                                     `json:"qty"`
	Weight             *float64                                `json:"weight,omitempty"`
	ProductWarehouse   productWarehouseDTO.ProductWarehouseDTO `json:"product_warehouse"`
}

// === Mapper Functions ===

func ToRecordingListDTO(e entity.Recording) RecordingListDTO {
	return toRecordingListDTO(e)
}

func ToRecordingListDTOs(e []entity.Recording) []RecordingListDTO {
	result := make([]RecordingListDTO, len(e))
	for i, r := range e {
		result[i] = ToRecordingListDTO(r)
	}
	return result
}

func ToRecordingDetailDTO(e entity.Recording) RecordingDetailDTO {
	listDTO := toRecordingListDTO(e)

	return RecordingDetailDTO{
		RecordingListDTO: listDTO,
		ProductCategory:  recordingProductCategory(e),
		Warehouse:        recordingWarehouseDTO(e),
		Depletions:       ToRecordingDepletionDTOs(e.Depletions),
		Stocks:           ToRecordingStockDTOs(e.Stocks),
		Eggs:             ToRecordingEggDTOs(e.Eggs),
	}
}

func ToRecordingDepletionDTOs(depletions []entity.RecordingDepletion) []RecordingDepletionDTO {
	result := make([]RecordingDepletionDTO, len(depletions))
	for i, d := range depletions {
		result[i] = RecordingDepletionDTO{
			ProductWarehouseId: d.ProductWarehouseId,
			Qty:                d.Qty,
			ProductWarehouse:   mapProductWarehouseDTO(&d.ProductWarehouse),
		}
	}
	return result
}

func ToRecordingStockDTOs(stocks []entity.RecordingStock) []RecordingStockDTO {
	result := make([]RecordingStockDTO, len(stocks))
	for i, s := range stocks {
		var usageAmount float64
		if s.UsageQty != nil {
			usageAmount = *s.UsageQty
		}
		var pendingQty float64
		if s.PendingQty != nil {
			pendingQty = *s.PendingQty
		}

		result[i] = RecordingStockDTO{
			ProductWarehouseId: s.ProductWarehouseId,
			UsageAmount:        usageAmount,
			PendingQty:         pendingQty,
			ProductWarehouse:   mapProductWarehouseDTO(&s.ProductWarehouse),
		}
	}
	return result
}

func ToRecordingEggDTOs(eggs []entity.RecordingEgg) []RecordingEggDTO {
	result := make([]RecordingEggDTO, len(eggs))
	for i, egg := range eggs {
		result[i] = RecordingEggDTO{
			Id:                 egg.Id,
			ProductWarehouseId: egg.ProductWarehouseId,
			Qty:                egg.Qty,
			Weight:             egg.Weight,
			ProductWarehouse:   mapProductWarehouseDTO(&egg.ProductWarehouse),
		}
	}
	return result
}

func toRecordingListDTO(e entity.Recording) RecordingListDTO {
	relation := toRecordingRelationDTO(e)

	var createdUser *userDTO.UserRelationDTO
	if e.CreatedUser != nil && e.CreatedUser.Id != 0 {
		mapped := userDTO.ToUserRelationDTO(*e.CreatedUser)
		createdUser = &mapped
	}

	return RecordingListDTO{
		RecordingRelationDTO: relation,
		CreatedAt:            e.CreatedAt,
		UpdatedAt:            e.UpdatedAt,
		CreatedUser:          createdUser,
		Kandang:              recordingKandangDTO(e),
		Location:             recordingKandangLocationDTO(e),
	}
}

func toRecordingRelationDTO(e entity.Recording) RecordingRelationDTO {
	latestApproval := defaultRecordingLatestApproval(e)
	if e.LatestApproval != nil {
		snapshot := approvalDTO.ToApprovalDTO(*e.LatestApproval)
		latestApproval = snapshot
	}

	return RecordingRelationDTO{
		Id:                   e.Id,
		ProjectFlock:         toRecordingProjectFlockDTO(e),
		RecordDatetime:       e.RecordDatetime,
		Day:                  intValue(e.Day),
		TotalDepletionQty:    floatValue(e.TotalDepletionQty),
		TotalDepletionCumQty: floatValue(e.TotalDepletionCumQty),
		CumDepletionRate:     roundFloatValue(e.CumDepletionRate, 2),
		DepletionRate:        roundFloatValue(e.DepletionRate, 2),
		CumIntake:            intValue(e.CumIntake),
		FcrValue:             floatValue(e.FcrValue),
		HenDay:               floatValue(e.HenDay),
		HenHouse:             floatValue(e.HenHouse),
		FeedIntake:           floatValue(e.FeedIntake),
		EggMass:              floatValue(e.EggMass),
		EggWeight:            floatValue(e.EggWeight),
		PopulationCanChange:  boolValueDefault(e.PopulationCanChange, true),
		TransferExecuted:     e.TransferExecuted,
		IsTransition:         boolValueDefault(e.IsTransition, false),
		IsLaying:             boolValueDefault(e.IsLaying, false),
		Approval:             latestApproval,
	}
}

func toRecordingProjectFlockDTO(e entity.Recording) RecordingProjectFlockDTO {
	result := RecordingProjectFlockDTO{
		ProjectFlockKandangId: e.ProjectFlockKandangId,
	}

	pfk := e.ProjectFlockKandang
	if pfk == nil {
		return result
	}

	if pfk.ProjectFlock.Id != 0 {
		result.FlockName = pfk.ProjectFlock.FlockName
		if pfk.ProjectFlock.Category != "" {
			result.ProjectFlockCategory = strings.ToUpper(pfk.ProjectFlock.Category)
		}
	}

	result.Period = pfk.Period

	if pfk.ProjectFlock.ProductionStandard.Id != 0 {
		result.ProductionStandart = &RecordingProductionStandardDTO{
			Id:              pfk.ProjectFlock.ProductionStandard.Id,
			Week:            recordingWeekValue(e),
			Name:            pfk.ProjectFlock.ProductionStandard.Name,
			HenDayStd:       floatValue(e.StandardHenDay),
			HenHouseStd:     floatValue(e.StandardHenHouse),
			FeedIntakeStd:   floatValue(e.StandardFeedIntake),
			MaxDepletionStd: floatValue(e.StandardMaxDepletion),
			EggMassStd:      floatValue(e.StandardEggMass),
			EggWeightStd:    floatValue(e.StandardEggWeight),
		}
	}

	if pfk.ProjectFlock.ProductionStandard.Id != 0 || e.StandardFcr != nil {
		result.Fcr = &RecordingFcrDTO{
			Id:     pfk.ProjectFlock.ProductionStandard.Id,
			Name:   pfk.ProjectFlock.ProductionStandard.Name,
			FcrStd: floatValue(e.StandardFcr),
		}
	}

	result.TotalChickQty = floatValue(e.TotalChickQty)

	return result
}

func recordingWeekValue(e entity.Recording) int {
	day := intValue(e.Day)
	if day <= 0 {
		return 0
	}
	weekBase := 1
	if isLayingRecording(e) {
		weekBase = config.LayingWeekStart()
	}
	return ((day - 1) / 7) + weekBase
}

func isLayingRecording(e entity.Recording) bool {
	if e.ProjectFlockKandang == nil {
		return false
	}
	return strings.EqualFold(e.ProjectFlockKandang.ProjectFlock.Category, string(utils.ProjectFlockCategoryLaying))
}

func recordingProductCategory(e entity.Recording) string {
	if e.ProjectFlockKandang == nil {
		return ""
	}
	project := e.ProjectFlockKandang.ProjectFlock
	if project.Id == 0 {
		return ""
	}
	if project.ProductionStandard.Id != 0 && project.ProductionStandard.ProjectCategory != "" {
		return strings.ToUpper(project.ProductionStandard.ProjectCategory)
	}
	if project.Category != "" {
		return strings.ToUpper(project.Category)
	}
	return ""
}

func recordingWarehouseDTO(e entity.Recording) *RecordingWarehouseDTO {
	pw := primaryProductWarehouse(e)
	if pw == nil || pw.Warehouse.Id == 0 {
		return nil
	}
	return mapWarehouseDTO(&pw.Warehouse)
}

func recordingKandangDTO(e entity.Recording) *RecordingKandangDTO {
	if e.ProjectFlockKandang == nil || e.ProjectFlockKandang.Kandang.Id == 0 {
		return nil
	}
	kandang := e.ProjectFlockKandang.Kandang
	return &RecordingKandangDTO{
		Id:       kandang.Id,
		Name:     kandang.Name,
		Status:   kandang.Status,
		Capacity: kandang.Capacity,
	}
}

func recordingKandangLocationDTO(e entity.Recording) *RecordingLocationDTO {
	if e.ProjectFlockKandang == nil || e.ProjectFlockKandang.Kandang.Id == 0 {
		return nil
	}
	location := e.ProjectFlockKandang.Kandang.Location
	if location.Id == 0 {
		return nil
	}
	return &RecordingLocationDTO{
		Id:      location.Id,
		Name:    location.Name,
		Address: location.Address,
	}
}

func primaryProductWarehouse(e entity.Recording) *entity.ProductWarehouse {
	if len(e.Stocks) > 0 {
		pw := e.Stocks[0].ProductWarehouse
		if pw.Id != 0 {
			return &pw
		}
	}
	if len(e.Depletions) > 0 {
		pw := e.Depletions[0].ProductWarehouse
		if pw.Id != 0 {
			return &pw
		}
	}
	if len(e.Eggs) > 0 {
		pw := e.Eggs[0].ProductWarehouse
		if pw.Id != 0 {
			return &pw
		}
	}
	return nil
}

func mapWarehouseDTO(wh *entity.Warehouse) *RecordingWarehouseDTO {
	if wh == nil || wh.Id == 0 {
		return nil
	}
	dto := &RecordingWarehouseDTO{
		Id:   wh.Id,
		Name: wh.Name,
	}
	if wh.Area.Id != 0 {
		dto.Area = &RecordingAreaDTO{
			Id:   wh.Area.Id,
			Name: wh.Area.Name,
		}
	}
	if wh.Location != nil && wh.Location.Id != 0 {
		dto.Location = &RecordingLocationDTO{
			Id:      wh.Location.Id,
			Name:    wh.Location.Name,
			Address: wh.Location.Address,
		}
	}
	return dto
}

func mapProductWarehouseDTO(pw *entity.ProductWarehouse) productWarehouseDTO.ProductWarehouseDTO {
	if pw == nil {
		return productWarehouseDTO.ProductWarehouseDTO{}
	}

	mapped := productWarehouseDTO.ToProductWarehouseDTO(pw)
	if mapped == nil {
		return productWarehouseDTO.ProductWarehouseDTO{}
	}

	return *mapped
}

func floatValue(value *float64) float64 {
	if value == nil {
		return 0
	}
	return *value
}

func roundFloatValue(value *float64, places int) float64 {
	if value == nil {
		return 0
	}
	if places <= 0 {
		return math.Round(*value)
	}
	factor := math.Pow(10, float64(places))
	return math.Round(*value*factor) / factor
}

func intValue(value *int) int {
	if value == nil {
		return 0
	}
	return *value
}

func boolValueDefault(value *bool, fallback bool) bool {
	if value == nil {
		return fallback
	}
	return *value
}

func defaultRecordingLatestApproval(e entity.Recording) approvalDTO.ApprovalRelationDTO {
	result := approvalDTO.ApprovalRelationDTO{}

	step := utils.RecordingStepPengajuan
	result.StepNumber = uint16(step)
	if label, ok := approvalutils.ApprovalStepName(utils.ApprovalWorkflowRecording, step); ok {
		result.StepName = label
	} else if label, ok := utils.RecordingApprovalSteps[step]; ok {
		result.StepName = label
	}

	if e.CreatedUser != nil && e.CreatedUser.Id != 0 {
		result.ActionBy = userDTO.ToUserRelationDTO(*e.CreatedUser)
	} else if e.CreatedBy != 0 {
		result.ActionBy = userDTO.UserRelationDTO{
			Id:     e.CreatedBy,
			IdUser: int64(e.CreatedBy),
		}
	}

	return result
}
