package dto

import (
	"time"

	entity "gitlab.com/mbugroup/lti-api.git/internal/entities"
	approvalDTO "gitlab.com/mbugroup/lti-api.git/internal/modules/approvals/dto"
	locationDTO "gitlab.com/mbugroup/lti-api.git/internal/modules/master/locations/dto"
	nonstockDTO "gitlab.com/mbugroup/lti-api.git/internal/modules/master/nonstocks/dto"
	supplierDTO "gitlab.com/mbugroup/lti-api.git/internal/modules/master/suppliers/dto"
	userDTO "gitlab.com/mbugroup/lti-api.git/internal/modules/users/dto"
	"gitlab.com/mbugroup/lti-api.git/internal/utils"
)

// === DTO Structs ===

type ExpenseRelationDTO struct {
	Id              uint64    `json:"id"`
	PoNumber        string    `json:"po_number"`
	TransactionDate time.Time `json:"transaction_date"`
}

type ExpenseBaseDTO struct {
	Id              uint64                           `json:"id"`
	ReferenceNumber string                           `json:"reference_number"`
	PoNumber        string                           `json:"po_number"`
	Category        string                           `json:"category"`
	Supplier        *supplierDTO.SupplierRelationDTO `json:"supplier,omitempty"`
	RealizationDate *time.Time                       `json:"realization_date,omitempty"`
	TransactionDate time.Time                        `json:"transaction_date"`
	Location        *locationDTO.LocationRelationDTO `json:"location,omitempty"`
}

type ExpenseListDTO struct {
	ExpenseBaseDTO
	GrandTotal     float64                          `json:"grand_total"`
	CreatedUser    *userDTO.UserRelationDTO         `json:"created_user,omitempty"`
	CreatedAt      time.Time                        `json:"created_at"`
	UpdatedAt      time.Time                        `json:"updated_at"`
	LatestApproval *approvalDTO.ApprovalRelationDTO `json:"latest_approval,omitempty"`
}

type ExpenseDetailDTO struct {
	ExpenseBaseDTO
	Documents       []DocumentDTO                    `json:"documents"`
	RealizationDocs []DocumentDTO                    `json:"realization_docs"`
	Kandangs        []KandangGroupDTO                `json:"kandangs,omitempty"`
	TotalPengajuan  float64                          `json:"total_pengajuan"`
	TotalRealisasi  float64                          `json:"total_realisasi"`
	CreatedAt       time.Time                        `json:"created_at"`
	UpdatedAt       time.Time                        `json:"updated_at"`
	CreatedUser     *userDTO.UserRelationDTO         `json:"created_user,omitempty"`
	LatestApproval  *approvalDTO.ApprovalRelationDTO `json:"latest_approval,omitempty"`
}

type ExpenseNonstockDTO struct {
	Id                    uint64                           `json:"id"`
	ExpenseId             *uint64                          `json:"expense_id,omitempty"`
	ProjectFlockKandangId *uint64                          `json:"project_flock_kandang_id,omitempty"`
	KandangId             *uint64                          `json:"kandang_id,omitempty"`
	NonstockId            *uint64                          `json:"nonstock_id,omitempty"`
	Qty                   float64                          `json:"qty"`
	Price                 float64                          `json:"price"`
	Notes                 string                           `json:"notes"`
	Nonstock              *nonstockDTO.NonstockRelationDTO `json:"nonstock,omitempty"`
	CreatedAt             time.Time                        `json:"created_at"`
}

type ExpenseRealizationDTO struct {
	Id                uint64                           `json:"id"`
	ExpenseNonstockId *uint64                          `json:"expense_nonstock_id,omitempty"`
	Qty               float64                          `json:"qty"`
	Price             float64                          `json:"price"`
	Notes             string                           `json:"notes"`
	Nonstock          *nonstockDTO.NonstockRelationDTO `json:"nonstock,omitempty"`
	CreatedAt         time.Time                        `json:"created_at"`
}

type KandangGroupDTO struct {
	Id         uint64                  `json:"id"`
	Name       string                  `json:"name,omitempty"`
	Pengajuans []ExpenseNonstockDTO    `json:"pengajuans,omitempty"`
	Realisasi  []ExpenseRealizationDTO `json:"realisasi,omitempty"`
}

type DocumentDTO struct {
	ID   uint64 `json:"id"`
	Path string `json:"path"`
}

// === MAPPERS ===

func ToExpenseRelationDTO(e entity.Expense) ExpenseRelationDTO {
	return ExpenseRelationDTO{
		Id:              e.Id,
		PoNumber:        e.PoNumber,
		TransactionDate: e.TransactionDate,
	}
}

func ToExpenseBaseDTO(e *entity.Expense) ExpenseBaseDTO {
	var location *locationDTO.LocationRelationDTO
	var supplier *supplierDTO.SupplierRelationDTO

	var realizationDate *time.Time
	if !e.RealizationDate.IsZero() {
		realizationDate = &e.RealizationDate
	}

	if e.Location != nil && e.Location.Id != 0 {
		mapped := locationDTO.ToLocationRelationDTO(*e.Location)
		location = &mapped
	}

	if e.Supplier != nil && e.Supplier.Id != 0 {
		mapped := supplierDTO.ToSupplierRelationDTO(*e.Supplier)
		supplier = &mapped
	}

	return ExpenseBaseDTO{
		Id:              e.Id,
		ReferenceNumber: e.ReferenceNumber,
		PoNumber:        e.PoNumber,
		Category:        e.Category,
		Supplier:        supplier,
		RealizationDate: realizationDate,
		TransactionDate: e.TransactionDate,
		Location:        location,
	}
}

func ToExpenseListDTO(e entity.Expense) ExpenseListDTO {
	var createdUser *userDTO.UserRelationDTO
	if e.CreatedUser.Id != 0 {
		mapped := userDTO.ToUserRelationDTO(*e.CreatedUser)
		createdUser = &mapped
	}

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

	grandTotal := calculateGrandTotal(&e)

	return ExpenseListDTO{
		ExpenseBaseDTO: ToExpenseBaseDTO(&e),
		GrandTotal:     grandTotal,
		CreatedUser:    createdUser,
		CreatedAt:      e.CreatedAt,
		UpdatedAt:      e.UpdatedAt,
		LatestApproval: latestApproval,
	}
}

func ToExpenseListDTOs(expenses []entity.Expense) []ExpenseListDTO {
	result := make([]ExpenseListDTO, len(expenses))
	for i, expense := range expenses {
		result[i] = ToExpenseListDTO(expense)
	}
	return result
}

func ToExpenseDetailDTO(e *entity.Expense) ExpenseDetailDTO {
	var documents []DocumentDTO
	var realizationDocs []DocumentDTO
	var createdUser *userDTO.UserRelationDTO
	if e.CreatedUser != nil && e.CreatedUser.Id != 0 {
		mapped := userDTO.ToUserRelationDTO(*e.CreatedUser)
		createdUser = &mapped
	}

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

	var pengajuans []ExpenseNonstockDTO
	var realisasi []ExpenseRealizationDTO

	for _, doc := range e.Documents {
		documents = append(documents, DocumentDTO{
			ID:   uint64(doc.Id),
			Path: doc.Path,
		})
	}

	for _, doc := range e.RealizationDocuments {
		realizationDocs = append(realizationDocs, DocumentDTO{
			ID:   uint64(doc.Id),
			Path: doc.Path,
		})
	}

	if len(e.Nonstocks) > 0 {
		pengajuans = make([]ExpenseNonstockDTO, 0)
		realisasi = make([]ExpenseRealizationDTO, 0)

		for _, ns := range e.Nonstocks {
			pengajuanDTO := ToExpenseNonstockDTO(ns)
			pengajuans = append(pengajuans, pengajuanDTO)

			if ns.Realization != nil {
				var nonstock *nonstockDTO.NonstockRelationDTO
				if ns.Nonstock != nil && ns.Nonstock.Id != 0 {
					mapped := nonstockDTO.ToNonstockRelationDTO(*ns.Nonstock)
					nonstock = &mapped
				}

				realisasiDTO := ExpenseRealizationDTO{
					Id:                ns.Realization.Id,
					ExpenseNonstockId: ns.Realization.ExpenseNonstockId,
					Qty:               ns.Realization.Qty,
					Price:             ns.Realization.Price,
					Notes:             ns.Realization.Notes,
					Nonstock:          nonstock,
					CreatedAt:         ns.Realization.CreatedAt,
				}
				realisasi = append(realisasi, realisasiDTO)
			}
		}
	}

	var totalPengajuan float64
	for _, p := range pengajuans {
		totalPengajuan += p.Qty * p.Price
	}

	var totalRealisasi float64
	for _, r := range realisasi {
		totalRealisasi += r.Qty * r.Price
	}
	kandangs := ToKandangGroupDTO(pengajuans, realisasi, e.Nonstocks)

	return ExpenseDetailDTO{
		ExpenseBaseDTO:  ToExpenseBaseDTO(e),
		Documents:       documents,
		RealizationDocs: realizationDocs,
		CreatedUser:     createdUser,
		Kandangs:        kandangs,
		TotalPengajuan:  totalPengajuan,
		TotalRealisasi:  totalRealisasi,
		CreatedAt:       e.CreatedAt,
		UpdatedAt:       e.UpdatedAt,
		LatestApproval:  latestApproval,
	}
}

func ToExpenseNonstockDTO(ns entity.ExpenseNonstock) ExpenseNonstockDTO {
	var nonstock *nonstockDTO.NonstockRelationDTO
	if ns.Nonstock != nil && ns.Nonstock.Id != 0 {
		mapped := nonstockDTO.ToNonstockRelationDTO(*ns.Nonstock)
		nonstock = &mapped
	}

	return ExpenseNonstockDTO{
		Id:                    ns.Id,
		ExpenseId:             ns.ExpenseId,
		ProjectFlockKandangId: ns.ProjectFlockKandangId,
		KandangId:             ns.KandangId,
		NonstockId:            ns.NonstockId,
		Qty:                   ns.Qty,
		Price:                 ns.Price,
		Notes:                 ns.Notes,
		Nonstock:              nonstock,
		CreatedAt:             ns.CreatedAt,
	}
}

func ToKandangGroupDTO(pengajuans []ExpenseNonstockDTO, realisasi []ExpenseRealizationDTO, nonstocks []entity.ExpenseNonstock) []KandangGroupDTO {
	kandangMap := make(map[uint64]*KandangGroupDTO)
	var directPengajuans []ExpenseNonstockDTO
	var directRealisasi []ExpenseRealizationDTO

	for _, p := range pengajuans {
		var kandangId uint64
		var kandangName string

		if p.KandangId != nil {
			kandangId = *p.KandangId
			for _, ns := range nonstocks {
				if ns.Id == p.Id && ns.Kandang != nil {
					kandangName = ns.Kandang.Name
					break
				}
			}
		}

		if kandangId > 0 {

			if kandangMap[kandangId] == nil {
				kandangMap[kandangId] = &KandangGroupDTO{
					Id:         kandangId,
					Name:       kandangName,
					Pengajuans: []ExpenseNonstockDTO{},
					Realisasi:  []ExpenseRealizationDTO{},
				}
			}
			kandangMap[kandangId].Pengajuans = append(kandangMap[kandangId].Pengajuans, p)
		} else {

			directPengajuans = append(directPengajuans, p)
		}
	}

	for _, r := range realisasi {
		var kandangId uint64
		var kandangName string

		for _, ns := range nonstocks {
			if ns.Realization != nil && ns.Realization.Id == r.Id && ns.Kandang != nil {
				kandangId = uint64(ns.Kandang.Id)
				kandangName = ns.Kandang.Name
				break
			}
		}

		if kandangId > 0 {
			if kandangMap[kandangId] == nil {
				kandangMap[kandangId] = &KandangGroupDTO{
					Id:         kandangId,
					Name:       kandangName,
					Pengajuans: []ExpenseNonstockDTO{},
					Realisasi:  []ExpenseRealizationDTO{},
				}
			}
			kandangMap[kandangId].Realisasi = append(kandangMap[kandangId].Realisasi, r)
		} else {
			directRealisasi = append(directRealisasi, r)
		}
	}

	// If there are direct expenses (without kandang), add them as a special entry with id=0
	if len(directPengajuans) > 0 || len(directRealisasi) > 0 {
		kandangMap[0] = &KandangGroupDTO{
			Id: 0,

			Name:       "",
			Pengajuans: directPengajuans,
			Realisasi:  directRealisasi,
		}
	}

	var kandangs []KandangGroupDTO
	for _, k := range kandangMap {
		kandangs = append(kandangs, *k)
	}

	return kandangs
}

func calculateGrandTotal(expense *entity.Expense) float64 {

	useRealization := expense.LatestApproval != nil && expense.LatestApproval.StepNumber >= uint16(utils.ExpenseStepRealisasi)

	if useRealization {

		var total float64
		for _, ns := range expense.Nonstocks {
			if ns.Realization != nil {
				total += ns.Realization.Qty * ns.Realization.Price
			}
		}
		return total
	}

	var total float64
	for _, ns := range expense.Nonstocks {
		total += ns.Qty * ns.Price
	}
	return total
}
